[
  {
    "path": "AUTHORS",
    "content": "\n     _________________________________________________________________\n\n                              Dillo project's team\n     _________________________________________________________________\n\n   Project maintainer:\n          * Jorge Arellano Cid\n\n   Core Developers:\n          * Jorge Arellano Cid\n          * Sebastian Geerken\n          * Johannes Hofmann\n          * Place (aka corvid)\n          * Luca Rota\n\n   Steady developers:\n          * Livio Baldini\n          * Eric Gaudet\n          * Jeremy Henty\n          * Jrgen Viksell\n\n   Contributors:\n          * Lars Clausen\n          * Ferdi Franceschini\n          * Matthias Franz\n          * Geoff Lane\n          * Sammy Mannaert\n          * James McCollough\n          * Justus Winter\n\n   Patches:\n          * Philip Blundell\n          * Francis Daly\n          * Sam Dennis\n          * Melvin Hadasht\n          * Madis Janson\n          * Frank de Lange\n          * Andrew McPherson\n          * Sean 'Shaleh' Perry\n          * Marcos Ramrez\n          * Adam Sampson\n          * Andreas Schweitzer\n          * Dominic Wong\n     _________________________________________________________________\n\n   Web site logo:\n          * Eric Gaudet\n          * Jarrod Henry\n\n   Gzilla author:\n          * Raph Levien\n     _________________________________________________________________\n\n\n\n\n-------------------\nUp to gzilla-0.1.7:\n-------------------\n\nRaph Levien <raph@acm.org> is the author of gzilla.\n\nChristoph Thompson <obituary@freshmeat.net> and Tomas O\"gren <stric@ing.umu.se>\n added pixmaps for the buttons and the code encessary to make them work.\n They also reorganized the source tree and gave general advice and tips.\n (Tomas will be pleased to find that my personal bookmarks file is no longer\n hard-coded into Gzilla. :))\n\nIan Main <slow@intergate.bc.ca> did the bookmarks.\n\nThanks to Otto Hammersmith, David Mosberger-Tang, and Peter Mattis for\npatches.\n\nContributions are always welcome!\n\n\n\n---------------\nNon-Dillo code:\n---------------\n\n* dw/fltkcomplexbutton.(cc|hh) contain code from the FLTK project whose\n  copyright is held by Bill Spitzak and others.\n* src/binaryconst.h contains code by Tom Torfs that the author placed in the\n  public domain.\n* src/md5.[ch] contain code by L. Peter Deutsch whose copyright is held by\n  Aladdin Enterprises.\n* src/tipwin.cc contains code by Greg Ercolano.\n"
  },
  {
    "path": "COPYING",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "ChangeLog",
    "content": "=============================================================================\nDillo project\n=============================================================================\n\nHere we list changes that are relatively significant and/or visible to the\nuser. For a history of changes in full detail, see our Mercurial repository\nat http://hg.dillo.org/dillo\n\n\ndillo-3.1 [not released yet]\n\n+- Floating elements.\n - Redesign of widget sizes (\"GROWS\").\n - Applied CSS attribute 'width' to all elements, 'height' is now also\n   supported.\n - Suport for 'min-width', 'max-width', 'min-height' and 'max-height'.\n - Suport for 'display: inline-block'.\n - <BUTTON>'s are now inline.\n - Image aspect ratio is preserved when one dimension is specified by a\n   percentage value.\n - New dillorc options 'adjust_min_width' and 'adjust_table_min_width'.\n - Make building of test/ files more robust.\n - Work on collapsing spaces: more cases supported.\n - Fix crash that's possible searching for text while page still being built.\n   Patches: Sebastian Geerken\n+- HTML5 character references.\n - Give images lower priority when requesting resources (responsiveness).\n - Reuse of connections for HTTP (disable w/ http_persistent_conns in dillorc).\n - Abort failed queries.\n - HTTP Strict Transport Security (disable with http_strict_transport_security\n   preference in dillorc).\n - Fix bug when closing popup using window manager (bug introduced in 3.0.3).\n - Block mixed content.\n - Improve cookies date recognition.\n - use mbed TLS ( https://tls.mbed.org/ )\n - Iterate through the IP addrs for a host when trying to connect().\n   Patches: corvid\n+- Doxygen fixes.\n   Patch: Jeremy Henty\n+- Move HTTPS from dpi into the browser, enable SNI, check more locations for\n   CA bundles and add --with-ca-certs-file and --with-ca-certs-dir to\n   configure, some improvement to security warning popups, etc.\n   Patch: corvid, Benjamin Johnson\n+- Fix bookmarks DPI crash.\n - Fix OSX compilation issue with xembed.\n   Patches: Johannes Hofmann\n\n-----------------------------------------------------------------------------\n\ndillo-3.0.5 [June 30, 2015]\n\n+- Image buffer/cache improvements.\n - Fix for segfault when there's no dpid and view source is requested.\n - Fix view-source dpi to handle null characters correctly.\n - Made view-source dpi use CSS formatting (it's shorter and cleaner).\n   Patches: Jorge Arellano Cid\n+- Crosscompile/buildroot-friendly fltk-config test.\n   Patch: Peter Seiderer\n+- Fix X11 icon name.\n - In location bar, tend toward showing beginning of URL instead of end.\n - Handle irix's version of vsnprintf().\n - INPUT, TEXTAREA placeholder attribute.\n - Better notification when user's domainrc settings block page redirection.\n - Fix bug with font_factor preference and CSS font-size:(larger|smaller).\n - Recognize Menu key in keysrc.\n - HTTPS: change cipher list to \"ALL:!aNULL:!eNULL:!LOW:!EXPORT40:!RC4\",\n   disable SSL3, disable TLS compression.\n   Patches: corvid\n+- Avoid requesting background images if an ancestor has display:none.\n - Ignore built-in search url if any are specified in dillorc.\n   Patches: Johannes Hofmann\n\n-----------------------------------------------------------------------------\n\ndillo-3.0.4.1 [December 24, 2014]\n\n+- Avoid a corner case segfault when no search URL is found in dillorc.\n   Patch: Sebastian Geerken, Jorge Arellano\n+- Fix linking problem with fltk-1.3.3 and fl_oldfocus.\n - Don't follow redirections or meta refresh in --local mode.\n   Patches: Jorge Arellano Cid\n+- Don't load background images in --local mode.\n - Make sure window is resizable with fltk-1.3.3.\n   Patches: Johannes Hofmann\n+- Remove Fl_Printer stub that always gave problems compiling under OSX.\n   Patch: corvid\n\n-----------------------------------------------------------------------------\n\ndillo-3.0.4 [April 09, 2014]\n\n+- OPTGROUP and INS elements.\n - Some HTML5 elements, etc.\n - Added show_ui_tooltip preference (BUG#1140).\n   Patches: corvid\n+- Make embedding into other applications more reliable (BUG#1127).\n - Add search from address bar.\n - Share CSS user agent stylesheet between pages.\n   Patches: Johannes Hofmann\n+- Better scaling (down) of images, even with consideration of gamma\n   correction.\n - Fixed (possibly security) problem of FltkImgBuf caused by integer overflow\n   (BUG#1129).\n - Some linebreaking fixes, and optimization for non-justified text, including\n   new preference stretchability_factor.\n - Added white_bg_replacement preference.\n - Implemented background images (except 'background-attachment'), added\n   load_background_images preference, as well as a new entry in the tools menu.\n   Patches: Sebastian Geerken\n+- Fix a set of bugs reported by Oulu Univ. Secure Programming Group\n   (HTML parsing, URL resolution, GIF processing, etc.)\n - Improved/fixed handling of HEAD, TITLE, TEXTAREA and form inputs.\n - Made show_url dillorc option work again (BUG#1128)\n   Patches: Jorge Arellano Cid\n+- Fix compiling on Hurd.\n   Patch: Pino Toscano\n+- Avoid Dpid children becoming zombies.\n   Patch: Jorge Arellano, J. Gaffney\n+- HTML5 WBR element.\n - Fix compiling on IRIX with MIPSpro compiler.\n   Patches: corvid, Sebastian Geerken\n\n-----------------------------------------------------------------------------\n\ndillo-3.0.3 [April 17, 2013]\n\n+- Support for CSS display property\n - Replace polling in DNS with a pipe.\n - Packed trie to optimize hyphenator memory consumption.\n - Fix crash in datauri dpi.\n - Speed up DNS requests when ipv6 isn't enabled.\n   Patches: Johannes Hofmann\n+- Fix image input coordinates (BUG#1070)\n - When location bar is given focus, temporarily show panels if hidden\n   (BUG#1093).\n - Fix bug where data URI has charset but no media type.\n - Bug meter line number fix for bare carriage returns.\n - Add some more info to various bug meter messages.\n - For text selection, fix releasing mouse button outside of boundary.\n - While selecting text, moving cursor outside viewport will scroll it.\n - Make form resetting work for <select>.\n - Never leave location bar empty when requesting page (BUG#1113).\n - Some improvements to tab navigation of form widgets (includes BUG#1111).\n - Don't let form input widget insert literal control chars (BUG#1110).\n - Assorted improvements to browser usability from the keyboard.\n - Added user interface color preferences (ui_*).\n - Removed show_url preference.\n   Patches: corvid\n+- Automatic hyphenation (includes penalty_* preferences that control\n   line-breaking).\n   Patch: Sebastian Geerken\n+- Added the \"view-source\" keybinding (default: Ctrl-U).\n   Patch: Alexander Voigt\n+- Introduced the domainrc mechanism for finer-grained control than\n   filter_auto_requests had provided.\n   Patch: p37sitdu, corvid\n+- After focusing option menu, keypress will seek to next one beginning with\n   that character.\n - When active tab is closed, focus the last one visited or opened.\n - Fixed a bug in dpip when dillo aborts a running dpi connection.\n   Patches: Jorge Arellano Cid\n+- Better window titles.\n - Show dialog if saved file would overwrite an existing one.\n   Patches: Jeremy Henty\n+- Remove hardcoded UI colors.\n   Patch: Benjamin Johnson, corvid\n+- Fix fd leak in dpi write failure case.\n   Patch: p37sitdu, Jorge Arellano Cid\n\n-----------------------------------------------------------------------------\n\ndillo-3.0.2 [December 05, 2011]\n\n+- Digest authentication\n   Patch: Justus Winter, corvid\n+- text-transform property\n - If not following redirection, show body of redirecting page.\n - Middle click on stylesheet menu item opens in new tab/window.\n - Improve handling of combining characters.\n - Locale-independent ASCII character case handling (fixes Turkic locales).\n   Patches: corvid\n+- Rework line breaking and fix white-space:nowrap handling.\n   Patch: Johannes Hofmann\n+- Bind Ctrl-{PageUp,PageDown} to tab-{previous,next}.\n   Patch: Jeremy Henty\n\n-----------------------------------------------------------------------------\n\ndillo-3.0.1 [September 21, 2011]\n\n+- Add preference for UI theme.\n - Allow key bindings for paging left/right.\n - Privacy -- never send cookies when making third-party requests, and\n   never accept cookies in the responses to these requests.\n   Patches: corvid\n+- Add show_quit_dialog dillorc option.\n   Patch: Johannes Hofmann\n\n-----------------------------------------------------------------------------\n\ndillo-3.0 [September 06, 2011]\n\n+- Ported Dillo to FLTK-1.3.\n   Patch: corvid, Johannes Hofmann, Jorge Arellano Cid\n+- Rewrote the User Interface: much simpler design and event handling.\n - Avoid double render after going Back or Forward (takes half the time now!).\n - Added on-the-fly panel resize (tiny/small/medium and normal/small icons).\n - Implemented a custom tabs handler (to allow fine control of it).\n - Rewrote dw's crossing-events dispatcher (avoids redundant events).\n - Fixed a years old bug: stamped tooltips when scrolling with keyboard.\n - Allow multiple search engines to be set in dillorc, with a menu in the web\n   search dialog to select between them.\n - Added an optional label to dillorc's search_url. Format: \"[<label> ]<url>\"\n - Fixed a border case in URL resolver: empty path + {query|fragment} (BUG#948)\n - Avoid a certificate dialog storm on some HTTPS sites (BUG#868).\n - Cancel the expected URL after offering a download (BUG#982)\n - Default binding for close-all changed from Alt-q to Ctrl-q.\n - Default binding for close-tab changed from Ctrl-q to Ctrl-w.\n - Add right_click_closes_tab preference (default is middle click).\n - 'hide-panels' key action now hides the findbar if present, and toggles\n   display of the control panels otherwise.\n - Removed 'large' option of panel_size preference.\n - Remove 'fullscreen' key action.\n - Eliminated a pack of 22 compiler warnings (gcc-4.6.1 amd64)\n - Lots of minor bug-fixes.\n   Patches: Jorge Arellano Cid\n+- Remove --enable-ansi configure option.\n - Limit saved cookie size.\n - Allow binding to non-ASCII keys and multimedia keys.\n - Enable line wrapping for <textarea>. (BUG#903)\n - Wrap image alt text.\n   Patches: corvid\n+- Add support for CSS adjacent sibling selectors.\n - Collapse parent's and first child's top margin.\n - Fix redraw loops and reenable limit_text_width dillorc option.\n   Patch: Johannes Hofmann\n+- Default binding for left-tab changed to Shift-Ctrl-Tab.\n   Patch: Jeremy Henty\n\n-----------------------------------------------------------------------------\n\ndillo-2.2.1 [July 18, 2011]\n\n+- Fix fullwindow start.\n - Implemented \"View source\" as a dpi.\n - Fix: vsource html, fix entities display, indentation.\n - Accept application/xhtml+xml.\n - Small caps support.\n - Border-collapse, border-style properties.\n - Removed gcc warnings for 64bit\n   Patches: Jorge Arellano Cid\n+- Configurable User-Agent HTTP header.\n   Patch: Alexander Voigt, corvid\n+- Include Accept header in HTTP queries.\n - Work with libpng-1.4.\n - Handle zero-width space.\n - Fix segfault closing window from WM.\n - Limit total number of cookies.\n - Use the suffix/subdomain field in cookies.txt.\n - Follow most specific matching rule in cookiesrc.\n - Fix segfault with form inputs and repush for stylesheets.\n - Handle white-space: pre-wrap and pre-line.\n - Support for the word-spacing property.\n - Fix segfault with https and self-signed certificates.\n - Text-indent property.\n   Patches: corvid\n+- Reintroduce bg_color dillorc option.\n - Make Dillo compile with Clang.\n - Fix Textblock flushing.\n - Support !important in style attributes.\n   Patches: Johannes Hofmann\n+- Implement line-height.\n - Draw image maps when image not loaded.\n   Patches: Johannes Hofmann, corvid\n+- Support @media rules.\n - Implement media-conditional @import rules.\n - Configure/Makefile cleanup.\n - Fix meta refresh looping.\n   Patches: Jeremy Henty\n\n-----------------------------------------------------------------------------\n\ndillo-2.2 [Feb 11, 2010]\n\n+- Added keybindings for scrolling.\n - Help button and local help file.\n   Patches: corvid, Jorge Arellano Cid\n+- Add support for multiple class names in CSS.\n - Fix X11 coordinate overflows with huge borders.\n - Improve CSS font parsing.\n - Enable font face setting via <font> element.\n - Ignore XML comment markers in CSS.\n - Split up long lines in plain.cc to avoid X11 coordinate overflows.\n - Fix user agent style for nested <ul>.\n - Add support for CSS property list-style-position.\n - Support border-width: thin | medium | thick.\n - Fix CSS_SHORTHAND_DIRECTIONS case in CssParser.\n - Add quirk to reset font properties in tables (fixes e.g. gmail).\n   Patches: Johannes Hofmann\n+- Cleaned up system includes in dpid directory.\n - Fixed CustProgressBox() for systems without weak symbols.\n - Handle signed chars. Added dIsspace() and dIsalnum() to dlib.\n - Added a_Dpip_get_attr_l() to DPIP's API.\n - Changed the CCCs to build in one step (for both HTTP and DPI). This\n   is simpler and helps to avoid race conditions.\n - Updated CCCwork.txt to the new scheme.\n - Fixed a bug with OPTION element (it was parsing entities twice).\n - Bugfix: remove the empty cache entry lingering after connection abort.\n - Switched capi to use dlib's Dlist instead of a_List_* methods.\n - Remove empty cache entries on Stop-button press and new link request!\n - Fixed URL unescaping in the datauri DPI.\n - Changed and reimplemented the DPI API.\n   * Fixed bugs and updated all DPI programs:\n   * Reimplemented the file dpi using select(). No pthreads-based anymore.\n   * Fixed ftp dpi: downloads, streamed transfer, error feedback.\n   * Fixed a bug in dillo with lingering cache entries.\n   * Made dpidc a C language program.\n   * Made the internal dsh implementation use unique functions for read/write.\n   * Removed the write/fwrite mix in DPIP.\n   * Made the DPIP API token-based. Packet assembling is coded inside DPIP!\n   * Several cleanups and more error handling sprinkled all over too.\n   Patches: Jorge Arellano Cid\n+- Fix segfault from AREA when MAP is missing name attribute.\n - Fix image map coordinates when margin/border/padding present.\n - Handle stylesheet @charset.\n - Fix cache segfault when cache entry removed.\n - Split words that contain whitespace as numeric character references.\n - Allow linebreaks around Chinese/Japanese characters.\n - Fix segfault in Html_parse_doctype (BUG#918).\n - Change exit code used for bad command line argument.\n - By default, do not use proxy for localhost (BUG 921).\n - Fix scrolling for text search.\n - Added 'save' key action (not bound by default).\n - Tooltips\n - Fix segfault when radio button lacks name attribute.\n - Enable popup menu below bottom of page content (BUG#856).\n - Handle JPEGs with CMYK color space.\n - Allow keysyms in keysrc.\n - Explicitly check installation bindir for dpid (BUG 930)\n - General cookies overhaul.\n   Patches: corvid\n+- Support for the letter-spacing property.\n   Patch: Johannes Hofmann, corvid\n+- Fixed a bug in w3c_mode. In fact it wasn't working at all.\n - Improve stylesheet menu.\n   Patches: Jeremy Henty\n+- Limit number of simultaneous connections (BUG 685).\n   Patch: Johannes Hofmann, Jorge Arellano Cid\n\n-----------------------------------------------------------------------------\n\ndillo-2.1.1 [Jul 3, 2009]\n\n+- Add additional size checks for images.\n   Patch: Jorge Arellano Cid, Johannes Hofmann, corvid\n+- Fixed a bug in parsing RGB color values (CSS).\n - Added support for css colors of the form rgb(255, 255, 255).\n - Assert that SimpleVector size is positive.\n   Patches: Johannes Hofmann\n+- Removed redundant system includes.\n - Added the \"nop\" keybinding (nop = NO_OPERATION; cancels a default hook).\n - Added 'stop' key action (not bound by default).\n - Fixed segfault when URL is NULL and dpis can't be found.\n   Patches: place (AKA corvid)\n+- Reduced 'warning: ignoring return value of ...'\n   Patch: Michal Nowak, Jorge Arellano Cid\n+- Check chdir() return code in Paths::init.\n - Removed return from a_Nav_unref_buf()\n - Do not build proto.c (file is empty); GCC warning\n   Patches: Michal Nowak\n\n-----------------------------------------------------------------------------\n\ndillo-2.1 [Jun 15, 2009]\n\n+- Added ipv6 addresses iteration and ipv4 fallback.\n   Patch: James Turner, Jorge Arellano Cid\n+- Added support for numeric IPv6 addresses entered into the url bar.\n - Made the DNS resolver report in numeric address notation.\n - Used the URL authority part instead of stripped default port in HTTP query.\n - Fixed Bookmarks modify's HTML so it wraps nicely on handhelds.\n   Patches: Justus Winter\n+- Implemented \"search previous\" in string searches.\n   Patch: Joo Ricardo Loureno\n+- Fix for file inputs without values (forms).\n - Tuned input width a bit.\n - Cleaned up resource embedding (forms)\n - Made cookierc parsing more robust.\n - Switched a_UIcmd_save() to take its URL from history (not location bar).\n - Set prefs.vw_fontname as default font for the UI.\n - Fix: recover page focus when clicking outside of a widget.\n - Fixed a segfault bug in the test/ directory.\n - Set middle click to submit in a new TAB. (Helps to keep form data!)\n - Added support for the Q element. BUG#343\n - Cleaned up Html_pop_tag().\n - Ported the command line interface from dillo1\n - Switched file dpi error messages to HTML.\n - Added a right-click menu to form controls (show hiddens, submit, reset)\n - Remove now-redundant generate_submit pref\n - Added the \"http_language\" dillorc option for setting HTTP's Accept-Language.\n - Refactored prefs.c to a much smaller size!\n - Fixed a SEGFAULT bug on redirections without Location.\n - Obey SELECT's size attribute.\n - Replace image loading button and page menu option with a tools menu option.\n - Implemented the \"overline\" text-decoration.\n - Enhanced and cleaned up text decorations for SUB and SUP.\n - Added \"View Stylesheets\" to the page menu.\n - Remove standard_widget_colors dillorc option.\n - Added dillo(1) man page.\n - Proxy support for HTTPS.\n - System config files have moved to sysconfdir/dillo/\n - Add keysrc.\n   Patches: place (AKA corvid)\n+- Switched SSL-enabled to configure.in (./configure --enable-ssl).\n - Standardised the installation of dpid/dpidrc with auto* tools.\n - Set the ScrollGroup as the resizable widget in downloads dpi.\n - Cleaned up and normalized D_SUN_LEN usage.\n - Fixed incorrect use of VOIDP2INT in Dialog_user_password_cb().\n - Ensure that the dlib dStr* functions are used everywhere.\n - Fixed a memory leak in Html_tag_open_link().\n - Fixed a memory leak in Klist().\n - Fix the comment for DLWin::del() (dpi/downloads.cc).\n - Removed redundant caller NULL checks already in the API.\n   Patches: Jeremy Henty\n+- Implemented Basic authentication!\n   Patch: Jeremy Henty, Jorge Arellano Cid\n+- Added \"-fno-rtti -fno-exceptions\" to CXXFLAGS (reduces binary size).\n   Patch: Jorge Arellano Cid, place (AKA corvid)\n+- Allowed compilation with older machines by removing a few C99isms.\n - Added use of inttypes.h when stdint.h isn't found.\n   Patches: Dan Fandrich\n+- Reduced warnings with gcc-4.3.\n   Patch: Thomas Orgis\n+- Made the parser recognize \"[^ ]/>\"-terminated XML elements.\n - Implemented basic CSS infrastructure.\n - Brought in Sebastian's CSS parser from dillo-0.8.0-css-3.\n - Read user style from ~/.dillo/style.css.\n - Added support for descendant and child selectors.\n - Improved CSS selector matching performance using hash tables.\n - Support selector specificity.\n - Add support for font-size and font-weight enum values.\n - Added \"font_max_size\", \"font_min_size\" dillorc options.\n - Add workaround for fltk bug #2062.\n - Reduce number of styleEngine::style0() calls.\n - Replace bg_color dillorc option.\n - Remove text_color, link_color, and force_my_colors dillorc options.\n - Fix CSS string parsing bug.\n - Replace visited_color dillorc option.\n - Add support for negative numbers in CSS parser.\n - Fix allow_white_bg dillorc option.\n - Load <style></style> content only if applicable.\n - Allow negative values for specific CSS properties only.\n - Disable negative margins for now as dw/* does not support them yet.\n - Support CSS @import directive.\n - Disable form widgets while stylesheets are loading.\n - Fix image scaling on reload with border, margin, or padding > 0.\n - Implement --xid command line option (used by claws mail client).\n - Make tab expansion in plain text utf8 aware.\n   Patches: Johannes Hofmann\n+- Updated the GPL copyright note in the source files.\n   Patch: Detlef Riekenberg\n+- Implemented a close-tab button for the GUI.\n   Patch: Joo Ricardo Loureno, Jorge Arellano Cid\n+- Added the \"middle_click_drags_page\" dillorc option.\n   Patch: Jorge Arellano Cid, Thomas Orgis\n+- Added configurable keybindings! (in ~/.dillo/keysrc)\n   Patch: Jorge Arellano Cid, Tim Nieradzik, place (AKA corvid)\n+- Fixed a memory leak with DilloImage structures.\n   Patch: Johannes Hofmann, place (AKA corvid)\n+- Set the File menu label to hide when the File menu-button is shown.\n - Set a new iconv() test in configure.in.\n - Allowed the rc parser to skip whitespace around the equal sign.\n - Fixed the parser not to call Html_tag_close_* functions twice.\n - Implemented loading of remote CSS Stylesheet.\n - Made a big cleanup of cache.c WRT charset decoding (fixes bugs).\n - Made an extensive cleanup/fixup of the whole image handling process.\n - Implemented the tools button with a couple CSS options.\n - Removed the nav.h dependency from html.cc\n - Made the repush() operation more general and suited for CSS use.\n - Fixed collapsing of whitespace entities in HTML mode.\n - Updated the URL resolver to comply with RFC-3986.\n - Fixed handling of META's content-type with no MIME type (e.g. only charset).\n - Added support for a quoted URL in META refresh.\n - Added instant client-side redirects (aka. zero-delay META refresh).\n   Patches: Jorge Arellano Cid\n\ndw\n\n+- Moved clicked from ButtonResource to Resource.\n   Patch: place (AKA corvid)\n+- Cleaned up unused code in fltkviewbase.\n   Patch: Johannes Hofmann\n+- Added lout/msg.h and normalized debug messages to use it.\n   Patch: Jorge Arellano Cid\n\n-----------------------------------------------------------------------------\n\ndillo-2.0 [Oct 14, 2008]\n\n+- Ported Dillo from GTK1 to FLTK2.\n - Ported a susbstantial part of the code from C to C++ (FLTK2 is in C++).\n - Wrote a new library: Dlib. With \"Dlib\" Dillo doesn't need glib anymore.\n - Ported all the code to Dlib.\n - Fixed Http_must_use_proxy() to be case insensitive.\n - Fixed some leaks and bugs in the cookies dpi.\n - Made Dillo's UI Control Panel resizable on-the-fly.\n - Implemented a new, simpler, dillorc parser.\n - Added handling of \"localhost\" in file URIs.\n - Fix: recognize \"http://foo\" and \"http://foo/\" as the same URL (BUG#497).\n - Reimplemented the Concomitant Callback chains into a uniform scheme!\n   (two query branches and a single answer branch). It simplifies a lot the\n   former CCC paths and allows for easier error control.\n - Added a new method for internally-generated urls: a_Cache_entry_inject().\n - Switched the cache to use Dlib's Dstr for its data storage.\n - Removed threads from IO. Now it only uses select-based watches.\n - Reimplemented IO.c and dpi.c to use Dlib's Dstr as its main buffer.\n - Turned Klist into a sorted list.\n - Removed one data-copy stage in Html_write_raw().\n - Switched gcc's \"fmt...\" syntax to ISO C __VA_ARGS__.\n - Fixed Dillo and its dpis to work from \"/tmp\" (for easy device unmount).\n - Simplified http.c by reusing the new non-blocking writes in IO.\n - Reworked the capi API so cache is only accessable from capi.\n - Rewrote the CCC's OpAbort handling.\n - Rewrote the DNS API and the Dpid start code inside Dillo.\n - Implemented Stop button to not only stop rendering but also networking.\n - Fixed the problem of scrolling position (remember position in a page).\n - Implemented a new scheme of scroll-position remembering. This is one per\n   visited page intead of one per url (this is more standard).\n - Fixed a subtle bug in klist that was affecting IO.\n - Fixed the position of the Bug Meter popup menu.\n - Hooked vertical scrolling to the mouse wheel.\n - Reimplemented plain.cc using a class, and hooked memory-release.\n - Reimplemented html.cc using a class, removed the linkblock,\n   and hooked memory-release to dw destruction.\n - Switched UI shortcuts from a global event handler to UI::handle.\n - Bound Ctrl+Space to toggle fullscreen mode.\n - Switched dillo to push a URL with fragment (anchor) into the stack.\n - Added a workaround for a CCC reentrancy segfault.\n - Bound FltkMultiLineTextResource to the html parser (TEXTAREA).\n - Added code to ignore the first <P> after <LI>.\n - Added a http_referer preference. See details in dillorc.\n - Added a text placeholder: \"[IMG]\" for img_off mode.\n - Fixed a SEGFAULT bug in http.c (handling of web->url).\n - Fixed handling of #anchors with repush, and other operations.\n - Implemented a_Dialog_choice5(). May be used by dpis and dillo.\n - Improved parsing of collapsing white space.\n - FTP dpi: Fixed algorithm bugs and improved the mime-type detector.\n - CCC: added reentrancy control to the OpEnd and OpAbort operations.\n - CCC: enhanced the debug function and implemented OpAbort for dpi.\n - Hooked a decoder for text/plain with charset.\n - Forbid dpi GET and POST from non dpi-generated urls.\n - Cleaned up a_Url_new().\n - Implemented tabbed browsing.\n   Patches: Jorge Arellano Cid\n+- Connected signals to <li> elements (fixes links within lists).\n - Enabled text, background-color, panel_size, geometry, fullscreen,\n   start_page, geometry offset, proxy_user and limit_text_width in preferences.\n - Enabled clicking over image links.\n - Improved notification upon leaving links.\n - Implemented image-link URL showing in status bar.\n - Added missing size-parsing for the <hr> element.\n - Hooked \"Activate\" to the form_receiver.\n - Connected the plain page context menu.\n - Added code for the image menu and hooked it to dw2 signals.\n - Hooked the page and link menus.\n - Added a image-loading toggle button to the UI.\n - Enabled hiding widgets of the control panel from dillorc.\n - Added a save-directory preference (save_dir in dillorc).\n - Fixed page-popup-menu to use the stack's top URL instead of base_url.\n - Added the \"static\" qualifier where missing.\n - Bound \"Copy link location\".\n - Bound preliminar find text support.\n - Added line numbers and enabled wrapping in the \"View Source\" window.\n - Added HTTP-1.1's chunked transfer support!\n - Made the stop button sensitive when loading an image.\n - Added more statics in dpi, const in pixmaps, and removed redundant includes.\n - Made cleanups in prefs (hiding local data/defs/symbols).\n - Fixed a segfault in cookies.c when no .dillo directory exists.\n - Added a MSG_HTTP for HTTP/1.1's warning headers.\n - Added support for multi-line header fields.\n - Added support for \"charset\" in the HTTP header field for Content-Type.\n - Added support for progressive display of progressive jpegs.\n - Fixed progressive display of interlaced pngs.\n - Enabled colspan=0 in tables parsing.\n - Fixed a memory leak in cookies.c\n - Added \"standard_widget_colors\" preference. It allows a more stylish look.\n - Fixed the return value of Cache_parse_multiple_field.\n - Added the multipart/form-data encoding method to form submission.\n - Fixed a bug in Html_parse_entity.\n - Fixed a bug in a_Url_cmp.\n - Fixed a bug in Cookies_parse_one. Set it to a single return point too!\n - Added dStr_memmem() and dStr_printable() to dlib.\n - Split Html_append_input() into smaller functions.\n - Implemented ISINDEX.\n - Added input image for FORMS.\n - Added button for FORMS.\n - Added nesting checks for BUTTON, SELECT and FORM.\n - Fix: shape=default is the background in a client-side image map.\n - Enabled client and server-side image maps.\n - Switched Window::destroy to Window::delete, fixing side effects.\n - Made zlib a configure requirement, and cleaned up configure.in.\n - Fixed a segfault bug in Nav.c.\n - Switched from charset to content-type for handling data.\n - Moved charset decoding into cache.\n - Implemented OBJECT as link (similar to FRAME).\n - Enabled the file dpi to look inside gzipped files.\n - Allowed form inputs outside the FORM element (it's in the standard).\n - Fixed a segfault bug in VERBATIM mode.\n - Made image inputs less of a special case by using x,y in ComplexButton.\n - Made forms show their action URL upon enter/leave mouse events (safety).\n - Fixed a memory leak in plain.cc.\n - Switched from DEBUG_MSG to MSG.\n   Patches: place (AKA corvid)\n+- Fixed a problem with locally-installed dpis.\n - Added code for optional image loading (nice interface) very advanced!\n - Added an experimental gzip decoder! \n - Implemented \"Load Images\" in the page menu and cleaned up html.hh.\n - Added shortcuts: PgDn=Spc, PgUp=b, Back=BackSpace, Forw=Shift+Backspace.\n - Made a cleanup in cache's parse header code.\n - Added support for \"charset\" in the META element.\n - Added a_Capi_get_flags(). It requests a cache entry's status as flags.\n - Switched URL_DATA type from char* to a dStr.\n - Implemented the file input control for forms.\n - Fixed data guesser to detect ASCII, LATIN1, UTF8, KOI8-R, CP-1251 as text.\n   Patch: place, Jorge Arellano Cid\n+- Fixed a cookies-related dillo freeze bug happening at:\n     http://www.fltk.org/newsgroups.php?gfltk.general+v:24912\n   Patch: Andreas Kemnade, Jorge Arellano Cid\n+- Fixed a va_list-related SEGFAULT on 64bit-arch in dStr_vsprintfa().\n   Added const declarations in html parser.\n   Patch: Vincent Thomasset\n+- Fixed void to int conversions for 64bit-arch.\n   Patch: Jorge Arellano Cid, higuita\n+- Set the url resolver to escape illegal chars instead of stripping.\n   Patch: Jorge Arellano Cid, Jeremy Henty\n+- Added suport for old iconv() (const char** as 2nd arg).\n   Patch: Jorge Arellano Cid, Christian Kellermann\n+- Added a strndup() replacement in dw2\n   Patch: Alexander Becher, Johannes Hofmann, Jorge Arellano Cid\n+- Fixed calcHashValue() to only return non-negative numbers (was SEGFAULT).\n - Improved scrolling performance on large pages by copying screen data\n    instead of rendering.\n - Updated configure.in to check only for fltk2-config.\n - Implemented drag-scrolling with the mouse's middle button.\n - Disabled double buffering (good for debugging redraws).\n - Switched dns.c from gethostbyname* to getaddrinfo (& removed libc5 code).\n - Made \"New browser window\" inherit the panel style of its parent.\n - Made TopGroup a PackedGroup, simplifying UI code and removing workarounds.\n - Added a redraw(DAMAGE_HIGHLIGHT) call to Back, Forw and Stop buttons.\n - Fixed a segfault bug when closing a bw under active networking.\n - Removed the unused SPCBuf variable.\n - Fixed a freeze-bug in IO.c where the IOwatch for reading was not removed.\n   Patches: Johannes Hofmann\n+- Made progress bars resize automatically.\n   Patches: Johannes Hofmann, Jorge Arellano Cid\n+- Improved FLTK library detection at configure time.\n   Patch: Frank Gevaerts\n+- Bound Ctrl-R to reload.\n - Made dialogs use font_factor (e.g. view source).\n - Implemented the SELECT element in FORMS!\n - Implemented MULTIPLE SELECT in FORMS.\n - Fixed a memory leak in nav.c\n!- html.cc cleanup (in progress). New classes, form API, source split.\n - Fixed a bug in style caching.\n   Patches: Jeremy Henty\n+- Added int32_t, EAI_NODATA and iconv tests for FreeBSD.\n   Patch: Thomas-Martin Seck\n+- Made CTRL-l focus the location bar instead of popping up a dialog.\n - Set key bindings with modifiers to work when alone only.\n - Replaced the findtext dialog with an in-window widget!\n   Patches: Justus Winter\n\n\n-----------------------------------------------------------------------------\ndw\n\n0.0.43\n        - Fixed bug in dw::core::ExtIterator (wrong mask, see also Jorge's\n          patch \"createvar.diff\" from Nov 08).\n        - Applied Jorge's patch for dw::core::AlignedTextblock\n          (\"lists.diff\" in mail from Nov 08).\n        - Applied Jorge's patch for dw::core::Textblock (\"links.diff\" in\n          mail from Nov 08).\n        - Applied Jorge's patch for configure.in (\"conf.diff\" in mail from\n          Nov 08).\n        - Renamed ExtIterator to DeepIterator.\n        - Implemented CharIterator (as an alternative to word iterators).\n        - Implemented text search (simple KMP based on CharIterator).\n        - Completed scrolling.\n          Patches: Sebastian Geerken\n\n        + Implemented drag scrolling with mouse's middle button.\n        - Enabled commented out partial image redraw, adding some checks.\n        - Enabled clipped redraws (avoids some flickering).\n        - Improvement: avoid complete redraws for child widget updates.\n        - Added code to really delete fltk2 widgets embedded in dw2.\n        - Fixed partial redraws and scrolling interference.\n        - Added combination of drawing rectangles into a larger one.\n        - Bug fix: a newly added rectangle may contain others.\n        - Made draw() check whether a rectangle is visible at drawing time.\n        - The background is now cleared properly on partial redraws.\n        - Made getWidgetAtPoint() a virtual method of widget and implemented a\n          custom one for TextBlock, reducing CPU usage on pages full of links.\n        - Added a style reference and an initialization to mustQueueResize.\n        - Replaced prepareCreateFltkWidget with an explicit call to add().\n        - Fixed an assertion-exit bug in DeepIterator.\n        - Fixed two viewport bugs: in drawing and scrolling.\n        - Made scrollbars really children of FltkViewport.\n        - Avoided multiple redraws when Layout::resizeIdle() queues itself.\n        - Set FltkViewBase::draw to intersect with view area for expose.\n        - Set cursor shape to CURSOR_MOVE on drag. Disabled drag over links.\n        - Added Layout::queueDrawExcept(), it reduces flickering by avoiding\n          a redraw when another rectangle is added.\n        - Fixed a scrollIdleId test to properly compare against -1.\n        - Set FltkPlatform::removeIdle to use removeRef() instead of remove().\n        - Cleaned up scroll code and moved updateCanvasWidgets() out of draw().\n        - Switched begin-end pairs with add() calls (fixes side-effect bugs!).\n        - Fixed checks in adjustScrollPos() to not allow wild values.\n        - Added double buffering for partial redraws!\n        - Implemented ComplexButton.\n        - Fixed find text so it works for phrases and PRE-wrapped text.\n        - Fixed a bug in DeepIterator::prev.\n        - Added the \"lout\" namespace.\n        - Reduced memory usage in 30% by reusing styles, reducing the size\n          of struct Content, and not preallocating in SimpleVector. !\n        - Made fontsTable and colorsTable static members of Font and Color.\n        - Moved highlighting information from struct Word into Textblock\n          to save memory.\n        - Reduced memory usage 10% with a custom memory handler in Textblock.\n        - Fixed a segfault when searching for single characters.\n        - Fixed memory leaks by s/delete/delete[]/ where necessary.\n        - Fixed three iterator memory leaks in Iterator::scrollTo().\n        - Changed DeepIterator to always clone its parameter (segfault bug).\n        - Implemented selection of multibyte glyphs (UTF-8).\n        - Removed the canvasWidgets list (fltk's children seem enough).\n        - Switched misc:assert() to the standard assert() call.\n          Patches: Johannes Hofmann\n        + Fixed a segfault-on-empty-strings bug in ConstString::hashValue.\n        - Fixed a segfault in reallocChildren (colspan/rowspan related).\n        - Fixed another assertion-exit bug in DeepIterator.\n        - Added the dw::fltk::ui::FltkMultiLineTextResource class.\n        - Implemented TEXTAREA using fltk::TextEditor.\n        - Bugfix: added the missing fltk::setfont calls before ::getwidth.\n        - Bugfix: initialized scrollIdleNotInterrupted variable.\n        - Commented out obsolete DEBUG_MSG lines in widget.cc.\n        - Fixed rowspan apportion when no single rowspan=1 row is found.\n        - Fixed allocateFltkWidget to handle and display FltkListResource.\n        - Fixed a slithery BUG in lout::misc::Stringbuffer.\n        - Implemented multiple item selection in FltkSelectionResource.\n          Patches: Jeremy Henty\n        + Added an extra argument in the link signals\n          (I recommended that instead of an array of image handlers --jcid)\n        - Aded an x_img camp to style (an image array index, like x_link).\n        - Added the same workaround in ui.cc for WHEN_ENTER_KEY_ALWAYS.\n        - Fixed shading (style.cc) and implemented FltkViewBase::drawPolygon().\n        - Implemented Circle and Disk bullet drawing.\n        - Fixed a bug in FltkViewBase::getClippingView.\n        - Made FltkColor::FltkColor use ::fltk::BLACK (bugfix).\n        - Fixed a bug with dissappearing widgets when scrolling with low CPU.\n        - Fixed a bug with the canvas offset of scrolling bars.\n        - Fixed a typo bug in scrollIdle() and a typo in processMouseEvent().\n        - Fixed an offset arithmetic bug with widgets inside textblock.\n        - Fixed RTFL debugging messages.\n        - Switched ComplexButton to use \"Activate\" instead of \"Clicked\" signal.\n        - Made the ComplexButton resource remember its click x,y.\n        - Added \"enter\" and \"leave\" signals into class Resource.\n          Patches: place\n        + Enabled mouse wheel scrolling. \n          FltkViewport::setScrollStep() sets how many points at a time.\n        - Added setDeleteCallback(DW_Callback_t func, void *data) to widget.\n          This allows to hook a callback when the widget is destroyed.\n        - Implemented a weighted apportionment algorithm for table rowspan.\n        - Implemented a weighted apportionment algorithm for table colspan.\n        - Implemented percentage widths in tables (better rendering!).\n        - Fixed an initialization bug in hruler.\n        - Fixed a bug in the textblock's wrapping algorithm.\n        - Fixed a bug in table cellpadding.\n        - Fixed a bug in getContentHeight().\n        - Changed the table-apportion algorithms + bug fixes. Big work!\n        - Fixed a mistake in the CSS-box-model PNG image (style-box-model.png).\n        - Added initialization for scrollX and scrollY.\n        - Fixed a typo bug in adjustScrollPos().\n        - Fixed two typo bugs in Textblock::drawLine().\n        - Changed Textblock::addText() to internally allocate its text string,\n          making the memory handling opaque to the caller.\n          Patches: Jorge Arellano Cid\n        + Added actual text selection.\n          Patch: Sebastian Geerken, place\n        + Implemented dw::fltk::ui::FltkOptionMenuResource::isSelected(),\n          added Item::createNewGroupWidget(), Item::createNewWidget().\n          Patch: Jeremy Henty, Johannes Hofmann\n        + Implemented the necessary base for image maps.\n          Patch: Johannes Hofmann, place\n\n0.0.42\n        - Fixed event handling in FLTK views. (Fixes links and several\n          problems with UI resources.)\n        - Implemented clipping views. (dw::Image used this already in\n          version 0.0.41.)\n        - Added \"activated\" signals to UI resources.\n          Patches: Sebastian Geerken\n\n-----------------------------------------------------------------------------\n\n\n0.8.5-pre-dw-redesign-1 [internal]\n- Prototype\n\ndillo-0.8.3-pre-dw-redesign-3 [Aug 30, 2004]\n - * Fixed bug GtkDwViewport, which caused some redraws to be ignored.\n   * Added GdkDwPreview.\n   Patches: Sebastian Geerken\n\n        \ndillo-0.8.3-pre-dw-redesign-2 [Aug 28, 2004]\n - * Added images to the current state of the redesign.\n     - New module Imgbuf, see doc/Imgbuf.txt for details.\n   Patch: Sebastian Geerken\n\n        \ndillo-0.8.3-pre-dw-redesign-1 [Aug 25, 2004]\n - * Introduced an abstraction layer between Dw and Gtk+. See README-port and\n     doc/DwRender.txt for more details.\n   Patch: Sebastian Geerken\n\n\n=============================================================================\nDillo project\n=============================================================================\n\n\ndillo-0.8.6 [Apr 26, 2006]\n\n - * Designed and implemented a dpi protocol library (libDpip.a in /dpip).\n   * Added a couple of new dpip commands.\n   * Fixed and uniformed the escaping of values inside dpip tags.\n   * Ported the bookmarks, download, file, https, ftp and hello plugins,\n     plus the dpid daemon and the rest of the source tree to use it.\n   * Improved the dpi buffer reception to handle split buffers (This was\n     required for handling arbitrary data streams with dpip).\n   * Fixed a bug in Cache_entry_remove_raw.\n   * Added a couple of \"const\" and C++ wrappers to dpiutil's API.\n   * Fixed a serious bug with the FTP plugin that led to two downloads of the\n     same file when left-clicking a non-viewable file.\n   * Added MIME/type detection to the FTP plugin, and removed popen().\n   * Set the dpi daemon (dpid) not to exit when the downloads dpi is running.\n   * Improved the accuracy of the illegal-character error reporting for URLs.\n   * Now save dialog replaces %20 and ' ' with '_' in the Filename suggestion.\n   * Made URL ADT automatically count and strip illegal characters from URLs.\n   * Added dpi/downloads.cc (Default FLTK2-based GUI for downloads dpi).\n   * Added \"./configure --disable-dlgui\" to build without FLTK2-GUI downloads.\n   * Fixed dpip's tag syntax and its parsing to accept any value string.\n   * Added DOCTYPE parsing (for better bug-meter error messages).\n   * Added a DOCTYPE type declaration tag to dpi-generated HTML.\n   * Fixed bookmarks dpi to escape ' in URLs and &<>\"' in titles (BUG#655).\n   * Added a check for malicious image sizes in IMG tags.\n   * Made the parser aware of buggy pages with multiple BODY and HTML elements.\n   * Fixed a bug in MIME content/type detection.\n   * Check HTTP Content-Type against real data (a security procedure).\n   Patches: Jorge Arellano Cid\n - * Added a datauri dpi to handle \"data:\" URIs (RFC-2397).\n   Patch: Jorge Arellano, Ben Wiley Sittler\n - * Moved the cookies management into a dpi server: cookies.dpi.\n   * Removed the restriction of only one dillo with cookies enabled!\n   * Fixed a bug with cookies for sites with self-signed certificate.\n   * Updated the cookies documentation.\n   * Made the downloads plugin dillo-cookie aware.\n   * Ported the cookies dpi to libDpip.a.\n   * Merged the new dpip code into the source tree.\n   Patches: Diego Senz, Jorge Arellano\n - * Added \"./configure --disable-threaded-dns\" (for some non reentrant BSDs).\n   Patch: Jorge Arellano, Francis Daly\n - * Fixed a bug with roman literals divisible by 10 (BUG#700).\n   * Fixed a bug with long alphabetically ordered lists (BUG#704).\n   Patch: Glyn Kennington\n - * Fixed a file descriptor leak in the dpi protocol library.\n   * Fixed a subtle segfault bug with malformed URLs in cookies.c.\n   Patch: Francis Daly\n - * Improved the dpi framework. Now dpi-programs can be specified in dpidrc,\n   and there's no need to touch dillo's sources to add new dpi services.\n   Just make your dpi program, add a dpidrc line and play with it!\n   Patch: Diego Senz, Jorge Arellano\n\n\ndillo-0.8.5 [Jun 15, 2005]\n\n - * Set \"file:\" to work as URI for current directory.\n   Patch: Diego Senz\n - * Added a \"small\" dillorc option for panel size (medium without labels).\n   Patch: Eugeniy, Jorge Arellano\n - * Fixed the shell escaping code in the ftp plugin.\n   * Added some checks for sane values in html.c.\n   * Added URL filtering to the ftp and downloads dpis to avoid SMTP hacks.\n   * Fixed the file dpi to react to the DpiBye command.\n   Patches: Jorge Arellano\n\n\ndillo-0.8.4 [Jan 11, 2005]\n\n - * Fixed a possible attack (program abortion) by malicious web pages, which\n     contain huge values for <table> attributes \"colspan\" and \"rowspan\".\n   * Changed anchors, they are now tested to be unique, and removed properly,\n     when a widget tree is changed (e.g. another page is visited). Also added\n     HTML warnings.\n   Patches: Sebastian Geerken\n - * Fixed two minor memory leaks (IO's Buf1Start & html's SPCBuf).\n   * Fixed handling of XML's \"/>\" tag-closing (e.g. <script ... />). BUG#514\n   * Removed obsolete code from IO/file.c.\n   * Added a few missing EINTR handlers in dpi.c.\n   * Orthogonalized the generic parser:\n       - Fixes memory leaks and widget state when recovering from bad HTML.\n       - Improves error detection and validation (needed by XHTML).\n       - Makes DOC tree generation possible (needed by CSS).\n       - Cleaner design of handling routines for bad HTML.\n       - Orthodox treatment of double optional elements (HTML, HEAD, BODY).\n       - Lots of minor code cleanups.\n   * Switched the dpi file server's design to pthreads (fixes a critical race).\n   * Avoided a crash when indexed GIF images lack a color map (BUG#647).\n   * Fixed a bug when the remote HTTP server sends no Content-Type and\n     the TCP packetizing splits the header from data (BUG#650).\n   * Returned the parser to the old whitespace \"collapsing\" mode\n     (this can be changed with the SGML_SPCDEL define in html.c).\n   * Fixed a memory leak for DwStyle (there was one leak per page).\n   Patches: Jorge Arellano\n - * Fixed a large memory leak of thread specific resources. --Very important\n   Patch: Jorge Arellano, Livio Baldini\n - * Removed warnings for pointer arithmetic and strict prototypes all\n     around the code (now it works under LP64 architectures).\n   * Made miscelaneous cleanups for LP64 architectures.\n   Patches: Jorge Arellano, Dieter Baron\n - * Changed dpid's umask to 0077.\n   Patch: Jorge Arellano, Richard Zidlicky\n - * Switched to g_vsnprintf (instead of vsnprintf).\n   Patch: Frank Wille\n - * Updated a bit the README file.\n   Patch: Dieter Baron\n - * Made a grammatical and typographical review of the whole documentation\n     in doc/. Also added some clarifications.\n   * Fixed a libpng detection problem (e.g., on CYGWIN). BUG#651\n   Patches: Roberto Sanchez\n - * Fixed \"id\" and \"name\" attributes parsing logic.\n   * Improved the parsing algorithm for character entities. BUG#605\n   Patches: Matthias Franz\n - * Fixed a security bug with uncertain data and a_Interface_msg().\n     CAN-2005-0012.\n   Patch: Tavis Ormandy\n\n\ndillo-0.8.3 [Oct 27, 2004]\n\n - * Added a missing error handler for unreachable host in http.c.\n   Patch: Dennis Schneider, Jorge Arellano\n - * Added fragment (aka anchor) decoding before it is set by Dw.\n   Patch: Matthias Franz, Jorge Arellano\n - * Fixed dpid to work even when a dpi directory is empty.\n   Patch: George Georgiev, Jorge Arellano\n - * Made the search dialog's URL go encoded in the query.\n   Patch: Matthias Franz\n - * Fixed the width of sized text entry widgets within FORMS.\n   Patch: Thorben Thuermer\n -(*)Made a library-based https dpi prototype, with certificate authentication!\n   * Separated the code in dpi/ so the common base now lies in dpiutil.c.\n   Patches: Garrett Kajmowicz\n - * Added SSL library detection code to configure.in.\n   Patch: Garrett Kajmowicz, Thomas-Martin Seck\n - * Fixed the wrong image-URL after cancelling a link-image popup (BUG#580).\n   * Improved the transfer speed for the bookmarks dpi when using 2.6.x linux.\n   * Fixed the downloads dpi to work when there're \"'\" characters in the URL.\n   * Fixed \" and ' characters stuffing in capi and interface for dpip commands.\n  (*)Added a \"dialog\" command to the dpi protocol (dpip). It allows any dpi to\n     make GUI choice-questions trough Dillo by using simple dpi tags.\n   * Merged some dialog-generating code in interface.c and fixed a bug with\n     the FORM repost dialog.\n   * Designed and implemented a unified API for handling data streams inside\n     dillo plugins. Servers and filters can use it.\n   * Converted the bookmarks, ftp, file, hello and the https prototype dpis\n     to the new dpiutil API.\n   * Replaced the old 'force_visited_color' dillorc option with the new\n     'contrast_visited_color' (using a renewed color-choosing algorithm).\n   * Added some 'undefined ASCII' to latin1 character conversions.\n   * Added the \"w3c_plus_heuristics\" option to dillorc.\n   * Removed a segfault bug on oversized images (rare case).\n   * Removed a CPU-hog on 302 redirections with cookies.\n   * Made HTTP's 302 redirections non-cacheable (incomplete).\n   * Implemented a new scheme for detecting redirection loops.\n   * Fixed cookies to accept four legacy old-date formats for \"Expires\".\n   Patches: Jorge Arellano\n - * Introduced a light-weight heuristic algorithm over the W3C parsing\n     scheme (allows for slightly better rendering: w3c_plus_heuristics=YES).\n   Patch: Rubn Fernndez\n - * Moved the internal support for \"file:\" URIs into a dpi (server plugin).\n   * Added TABLE-based rendering of directory listings to the new file dpi.\n   Patches: Jorge Arellano, Jrgen Viksell\n - * Removed DwWidget::realize and DwWidget::unrealize.\n   * Made all signals expect events to abstract methods.\n   * Renamed a_Dw_widget_{size_request|get_extremes|size_allocate|set_*} to\n     p_*, they should not be used outside of Dw.\n   Patches: Sebastian Geerken\n - * Fixed the meta refresh warning to not switch from IN_HEAD to IN_BODY.\n   Patch: Bjrn Brill\n\n\ndillo-0.8.2 [Jul 06, 2004]\n\n - * Made PgUp/PgDn scroll by a full page, instead of a half (BUG#418).\n   * Added new Gtk+ widgets GtkExtButton, GtkExtMenu, and GtkExtMenuItem.\n     - Used GtkExtButton to enhance the button 3 menu of the forward button,\n       backward button and bug meter buutton.\n     - GtkExtMenu and GtkExtMenuItem are used to make handling button 2\n       in the history menus cleaner.\n   * Made bug meter button react on high-level \"clicked\" signal, instead of\n     \"button-press-event\".\n   * New widget GtkMenuTitle, used for nicer titles in menus.\n   Patches: Sebastian Geerken\n - * Added a small handler for javascript links (BUG#546).\n   * Made the parser ignore white space immediately after an open tag, and\n     immediately before a close tag.\n   * Fixed handling of redirection answers with unviewable MIME type (BUG#563).\n   * Fixed the history-stack handling after redirection chains.\n   * Fixed the character escaping logic in directory listings (BUG#564).\n   * Added a small hack to view UTF-8 encoded quotation marks.\n   * Added a \"start_page\" option to dillorc (to override the splash screen).\n   * Tuned the buffering scheme for local directory listings (more speed).\n   * Set some initial socket-buffering for dpis (in dpid).\n   * Disallowed the \"about:\" method on non-root URLs (BUG#573).\n   * Made the rendered area keep its focus after a form submition.\n   * Fixed some include files in src/IO/.\n   Patches: Jorge Arellano\n - * Added hints (icons and tooltip text) to buttons featuring a right-click\n     context menu.\n   * Now you can copy & paste an URL into the \"Clear URL\" button.\n   Patch: Jorge Arellano, Sebastian Geerken\n - * Made the save and open file dialogs remember the last directory (BUG#211).\n   Patch: Diego Senz\n\n\ndillo-0.8.1 [May 14, 2004]\n\n - * Fixed dirent.h includes inside dpid.\n   Patch: Joseph Myers\n - * Fixed a slippery bug with certain interlaced gif images (BUG#500).\n   Patch: Andreas Mueller\n - * Fixed libpng-1.2.4 detection in configure.in.\n   Patch: Rubn Helguera\n - * Added proxy authentication support through the \"http_proxyuser\" option\n     in dillorc (the password is asked at run time).\n   Patch: Ivan Daniluk, Jorge Arellano, Francis Daly\n - * Moved tooltips to DwStyle, tooltip event handling to DwPage, and applied\n     this also to the TITLE attribute of <a> and <abbr>.\n   Patch: Jrgen Viksell, Sebastian Geerken\n - * Fixed a bug related to spaces after anchors and breaks.\n   Patch: Sebastian Geerken\n - * Removed two \"type punning\" gcc warnings (dw_gtk_viewport.c).\n   * Added some missing \"static\" qualifiers.\n   * Improved a_Strbuf_chars() so no list reversion is required.\n   * Removed an unused data list (dns.c), and redundant code (selection.c).\n   * Switch one realloc() call to g_realloc(), to match g_free() (dpi.c).\n   * Removed unnecessary NULL-checks and NULL initializations.\n   * Added Html_get_attr_wdef(), it lets providing a default return value.\n   Patches: Jrgen Viksell\n - * Fixed configure.in so pthreads are only linked where needed.\n   Patch: Jrgen Viksell, Jorge Arellano\n - * Modified a_Misc_stuff_chars for simplicity and removed a memory leak.\n   * Made the dpi framework send the HTTP query to the https dpi\n     (this allows for an SSL-lib dpi and for easier session caching).\n   * Cleaned up the int2void and void2int casts in CCC parameters.\n   * Added container|inline model information to the HTML element table, and\n     made the bug-meter and the parser aware of it. This both improves bug\n     detection and rendering. \n   * Fixed newly detected HTML bugs in bookmarks dpi and file.c.\n   * Fixed opening files with a ':' character in its name (again).\n   * Added binaryconst.h (allows for binary constants in C).\n   * Fixed The ladder effect with lists (BUG#534).\n   * Made the bug-meter detect tags lacking a closing '>' (BUG#532).\n   * Made the bug-meter detect excluded inline elements from <PRE>.\n   * Eliminated a segfault source with tricky <input> tags (BUG#552).\n   * Fixed <address> to render as a block element (BUG#527).\n   * Added a content test for \"name\" and \"id\" attribute values (BUG#536).\n   * Fixed the URL resolver handling of the \"//\" sequence in <path> (BUG#535).\n   * Added \"show_extra_warnings\" and removed \"use_old_parser\" (dillorc).\n   * Added minor support for the deprecated <MENU> element.\n   * Eliminated a race condition that produced segfaults when a dpi transfer\n     was cancelled before the contents were sent (a very rare case).\n   * Added a test for socklen_t in configure.in.\n   * Fixed the downloads dpi to handle both new savenames and target directory.\n   * GdkRgb: fixed handling of not usable system default visual and colormap.\n   * Made dillo recognize unhandled MIME types, and offer a download dialog!\n   Patches: Jorge Arellano\n\n\ndillo-0.8.0 [Feb 08, 2004]\n\n - * Added a right-mouse-button popup for images!\n   Patch: Frank de Lange, Eric Gaudet, Jorge Arellano\n - * Made main document window grab focus on startup, fullwindow,\n     and after open url (BUG#330)\n   * Set Ctrl-U to focus the location entry,\n     Ctrl-R to reload, and Ctrl-H to hide controls.\n   Patches: Johan Hovold, Jorge Arellano, Stephan Goetter\n - * Added a missing handler for broken-connection condition.\n   Patch: Jorge Arellano, Phil Pennock\n - * Introduced a new way of handling dillo plugins! Now the\n   communications and managing is done by a daemon: dpid.\n   This comes with a lot of advantages described in Dpid.txt.\n   Patch: Programming: Ferdi Franceschini; Design: Jorge Arellano\n - * Wrote documentation for dpid (Dpid.txt).\n   * Removed a memory leak in Get_line().\n   Patches: Jorge Arellano, Ferdi Franceschini\n - * Developed a plugin for downloads. It uses wget and can handle several\n   connections at the same time.\n   * Developed stress tests for both dpid and the downloads dpi.\n   Patches: Ferdi Franceschini\n - * Adapted dpi.c to manage plugins through dpid.\n   * Improved the incoming dpi-stream processing to accept images from a dpi.\n   * Added/updated lots of dpi-related comments.\n   * Updated the dpi1 spec.\n   * Removed the forced end-to-end reload that was set upon dpis. Now,\n     dpi-generated pages can be cached.\n   * Made dillo able to handle multiple plugins (still lacks a dynamic API)\n   * Wrote bare bones plugins for handling: FTP and HTTPS.\n   * Wrote an example plugin: HELLO  --kind of \"Hello world\".\n   * Made all the bindings to make it work (fully commented).\n   * Added code for automatical and non-blocking dpid start!\n   * Added an extensible secondary URL convenience for popup menus.\n   * Attached the image popup to the link menu (when the link is an image).\n   * Removed a memory leak in the selection code (commands.c).\n   * Cleaned up memory handling when reusing the GioChannel for IPv6.\n   * Removed a race-condition-polling-CPU-hog bug in IO! (hairy... ;)\n   * Adapted the generic parser to make HTML error detection, providing\n     the line number and a hint (expected tag) in the error message!\n   * Added a bug-meter button that shows the count of detected HTML errors\n     (left click shows the errors, right click offers a validation menu).\n   * Added information about optional, required and forbidden end tags.\n   * Modified the parser's handling of closing tags to account for elements\n     with an optional close tag, and for more accurate diagnosis messages.\n   * Added 'use_old_parser' option to dillorc (boolean).\n   * Fixed the handling of HEAD and BODY elements to account for their\n     double optional condition (both open and close tags are optional).\n   * Added the MSG() macro to msg.h and replaced g_print() with it.\n   * Added the \"show_msg\" dillorc option to disable MSG().\n   * Increased the number of warning messages reported by gcc.\n   * Solved a lot of signed/unsigned problems.\n   * Made the necessary cleanups/bug-removals for the new warning level.\n   * Connected the dpi stream to the cache using the CCC!\n   * Fixed the cache API by introducing the new call a_Capi_get_buf().\n   * Fixed a race condition and a multiple request problem.\n   * Cleaned up the code for the progressbar widgets.\n   * Standarized unix domain sockets with AF_LOCAL for portability.\n   * Minor cleanups for a smooth compile on older systems (libc5).\n   * Fixed the handling of P element for the HTML nesting checks.\n   * Set Ctrl-B for bookmarks shortcut (instead of Alt-B).\n   Patches: Jorge Arellano\n - * Enhanced the speed of the actual selection of text.\n   * Added command line option --debug-rendering.\n   * Added \"button_sensitive\" attribute to DwWidget, which is needed to\n     make <BUTTON>'s accessable at all. (They were inaccessable since the\n     introduction of text selection!)\n   * Changed behaviour of DwButton, see NOTE at beginning of dw_button.c.\n   * Added \"collapsing margins\" to DwPage.\n   * Added CSS \"list-style-type\" and \"display\" equivalents to DwStyle, changed\n     definition of \"font\", replaced \"nowrap\" by \"white-space\", and renamed\n     \"link\" to \"x_link\".\n   * DwBullet now uses DwStyle for the bullet type, made necessary changes\n     in HTML parser.\n   * Changed DwStyleLength, now only pixel values and percentages are\n     supported. (For CSS, anything else will be done elsewhere.)\n   * Added word backgrounds to DwPage (not yet used.)\n   * Added the possibility to clip widget drawings (new function\n     p_Dw_widget_will_clip).\n   * Made images showing the ALT text as long as no image data has been\n     retrieved.\n   * Cleaned up event handling and related code: \"link_*\" signals now return\n     gboolean, and DwWidget events are signals.\n   * Moved DwRectangle and related to dw.c.\n   * Rewrote idle drawing, fixed BUG#202.\n   * Removed p_Dw_widget_queue_clear*.\n   * Added --enable-rtfl option to configure.\n   * Fixed a bug in findtext (wrong highlighting).\n   * Many changes in scrolling: added x coordinate (except for anchors), and\n     DW_[VH]POS_INTO_VIEW position. Added x coordinate also to DilloUrl.\n   Patches: Sebastian Geerken\n - * Fixed bug in DwImage::link_clicked signal.\n   Patch: Stephan Goetter, Frank de Lange (simultaneously and independent :-)\n - * Fixed memory leak in Html_tag_open_isindex.\n   * Added numerical keypad cursor keys navigation.\n   * Changed return values of Dw event methods from gint to gboolean.\n   * Cleaned up debug message generation by using glib wrappers.\n   * Replaced DwStyle::SubSup by new DwStyleVAlignType values, and\n     DwStyle::uline and DwPage::strike by new DwStyle::text_decorations.\n   * Added new convenience macros DW_WIDGET_HEIGHT, DW_WIDGET_CONTENT_HEIGHT,\n     and DW_WIDGET_CONTENT_WIDTH.\n   * Added configure options to disable either: png, jpeg or gif.\n   * Fixed configure.in for proper library linking for dpis and dpid.\n   * Improved libpng detection.\n   Patches: Jrgen Viksell\n - * Fixed wrong handling of coordinates in ISMAP and USEMAP images.\n   * Added a hand-shaped cursor to input controls of type image.\n   * Fixed a off-by-one memory leak in Dw(Ext)Iterator.\n   * Fixed NULL result handling of p_Dw_widget_text_iterator() in DwBullet,\n     DwHRuler and DwImage.\n   * Made dpid/Makefile.am aware of $(DESTDIR).\n   * Fixed wrong return value of a_Findtext_search for widget == NULL.\n   Patches: Frank de Lange\n - * Fixed a bug in Dw cursor code.\n   Patch: Frank de Lange, Sebastian Geerken\n - * Corrected marshal functions for DwWidget signals.\n   Patch: Anders Gavare, Sebastian Geerken\n - * Added support for anchors using the \"id\" attribute (BUG#495).\n   * Defined dillo's version-string in one place only: configure.in.\n   Patch: Francis Daly\n - * Removed a segfault source with corrupted MIME types in HTTP (BUG#501).\n   * Made SPAM-safe URLs aware of image buttons (BUG#508).\n   Patch: Francis Daly, Jorge Arellano\n - * Added a web search dialog (with toolbar icon, shortcut: Ctrl-S).\n     The search engine can be set in dillorc (defaults to google).\n   Patch: Johan Hovold, Jorge Arellano\n - * Fixed a problem with libpng options detection (configure.in).\n   Patch: Rubn Fernndez\n - * Added \"pthreads\" (with an \"s\") detection to configure.in.\n   Patch: Andreas Schweitzer\n - * Added the \"-geometry\" switch to the CLI.\n   Patch: Jorge Arellano, Jan Dittmer\n\n\ndillo-0.7.3 [Aug 03, 2003]\n\n - * Some more selection goodies:\n     - Redesign of the selection state model, now the selection is preserved\n       as long as possible.\n     - Highlighted text is now drawn inverse (new DwWidget::bg_color).\n     - Selection of images, list bullets and hrulers (as text), with a common\n       text iterator for the respective widgets.\n   * Borders may now be drawn inverse (needed for selection).\n   * Improved the speed when selecting large areas. (BUG#450)\n   * Fixed a bug in DwPage extremes.\n   * Fixed a wrong implementation of incremental resizing for DwPage.\n     (Affected functions: Dw_page_rewrap and a_Dw_page_add_widget)\n   * Fixed a bug in a_Dw_widget_size_allocate.\n   * Made jumping to anchors faster (removes CPU hog).\n   * Fixed a bug in Dw_page_get_extremes().\n   * Made (invalid) <li>'s without <ol> or <ul> defined, and independent of\n     each other.\n   * Fixed rendering of <frameset>.\n   Patches: Sebastian Geerken\n - * Made a new set of toolbar icons!\n   Patch: John Grantham (http://www.grantham.de/)\n - * Added support for the hspace and vspace attributes of the IMG tag.\n   * Made only left button activate the image input type (BUG#367,#451).\n   * Fixed SELECT with \"multiple\" but without \"size\" (BUG#469).\n   Patches: Jrgen Viksell\n - * Added titles to bookmark server's html pages.\n   Patch: Kelson Vibber\n - * Made IFRAME to be handled like FRAME (shows link).\n   Patch: Nikita Borodikhin, Jorge Arellano\n - * Fixed a bug in 'a_Misc_stristr' that permeated findtext. (BUG#447)\n   Patch: Jorge Arellano, \"squirrelblue\"\n - * Finished handling of single and double quotes inside dpi tags.\n   * Fixed a bug for named-entities' character codes greater than 255.\n   * Introduced a small UCS to Latin1 converter to help rendering.\n   * Added a check for Unix98's \"socklen_t\" (BUG#466).\n   * Added the missing EINTR handlers in IO.c and file.c.\n   * Fixed the problem of adding garbage anchors.\n   Patches: Jorge Arellano\n\n\ndillo-0.7.2 [Apr 30, 2003]\n\n - * Implemented text selection! (aka: Copy&Paste) (BUG#59)\n   Patch: Sebastian Geerken, Eric Gaudet\n - * Fixed IPv6 support when the unthreaded server is used.\n   Patch: Damien Couderc, Jorge Arellano\n - * Fixed the IPv6 socket connection code for *BSD.\n   Patch: Daniel Hartmeier, Jorge Arellano\n - * Made the URL_SpamSafe flag be inherited by the BASE element.\n   Patch: Melvin Hadasht\n - * Switched configure.in to use AC_CANONICAL_SYSTEM instead of 'uname'.\n   Patch: Patrice Mandin\n - * Added \"image/x-png\" to MIME types (obsolete, but should be recognized).\n   Patch: Paolo P.\n - * Fixed the code that handled the installation of \"dillorc\".\n   Patch: Andreas Schweitzer\n - * Fixed a lot of glitches in configure.in: notably libpng and libjpeg\n   detection, enabling and disabling. (BUG#: 386, 407, 392, 349)!\n   Patches: Andreas Schweitzer\n - * Fixed two leaks in Dw(Ext)Iterator.\n   Patches: Jrgen Viksell\n - * Repaired some minor misbehaviours in the cookie-strings parser.\n   Patches: Jrgen Viksell, Jorge Arellano\n - * Enabled entities parsing in HTML-given hidden and password values.\n   Patch: Jorge Arellano, Francis Daly\n - * Implemented character stuffing in dpi (Fix bookmarks with quotes) BUG#434.\n   * Added a HTML warning message for META outside HEAD.\n   * Removed a segfault source when the server doesn't send content/type info.\n   * Added file type detection for filenames without extension.\n   * Removed the warnings detected with gcc 3.2.2.\n   * Fixed the VERBATIM parsing mode and replaced the SCRIPT mode with it.\n   * Fixed the problem with CR handling in TEXTAREA (BUG#318).\n   * Fixed initial value parsing within TEXTAREA tags (BUG#400).\n   * Fixed loading files with spaces in the name (command line) BUG#437.\n   Patches: Jorge Arellano\n\n\ndillo-0.7.1.2 [Mar 11, 2003]\n\n - * Fixed a bug in the bugfix that used uninitialized memory contents\n     causing all kind of undesirable side effects.\n   Patch: Andreas Schweitzer\n\n\ndillo-0.7.1 [Mar 10, 2003]                      -- bugfix release\n\n - * Fixed the setting of the FD_CLOEXEC flag.\n   Patch: Raphael Barabas\n - * Added an automatic file-locking alternative for systems lacking flock().\n   Patch: Yang Guilong\n - * Fixed a memory leak with pixmaps.\n   Patch: Keith Packard\n - * Fixed the link color switch with scroll wheel mouses (BUG#348)\n   Patch: Stephen Lewis\n - * Made the bookmarks server keep a backup file: bm.txt.bak.\n   * Fixed not loading the bookmarks file (and erasing the bookmarks).\n   * Added some missing EINTR handlers.\n   * Added a handler for unresponsive dpi sockets!\n   * Restricted dpi-requests to dpi-generated pages only.\n   * Used -1 instead of WAIT_ANY (some systems don't have it). (BUG#413)\n   * Fixed a source bug when G_DNS_THREADED is not defined. (BUG#421)\n   * Switched sprintf to g_snprintf which is safer.\n   Patches: Jorge Arellano\n\n\ndillo-0.7.0 [Feb 17, 2003]\n\n - * Added IPv6 support! [./configure --enable-ipv6] (BUG#351)\n   Patch: Philip Blundell\n - * Fixing char escaping/encoding problems with file URIs (BUG#321)\n   * Fixing buffer overflow sources in file.c.\n   * Switched the image tooltip from \"alt\" to \"title\" attribute.\n   Patch: Francis Daly, Jorge Arellano\n - * Added code so that tooltips stay within the screen.\n   Patch: Pekka Lampila, Sebastian Geerken\n - * Fixed a problem occurring when scrolling with the \"b\" key.\n   Patch: Livio Baldini\n - * Fixed a memory leak in DwAlignedPage.\n   Patch: Jrgen Viksell, Sebastian Geerken\n - * Moved stuff into remove_cookie() and add_cookie() functions.\n   * Made cookies sort once in add_cookie().\n   * Removed some unneeded casts and calls in cookies.\n   * Repairing some minor misbehaviours in Cookies_parse_string().\n   Patches: Jrgen Viksell, Jorge Arellano, Madis Janson\n - * Fixed a bug in Dw_widget_mouse_event.\n   Patch: Jrgen Viksell\n - * Fixed a bug in DwPage (\"height\" argument).\n   Patch: Pekka Lampila\n - * Removed a segfault source in http.c\n   Patch: Madis Janson\n - * Removed space around tables.\n   * Implemented the <button> tag! (BUG#276)\n   * Added iterators (DwItetator, DwExtItetator, DwWordItetator).\n     - Rewrote findtext, added highlighting and \"case sensitive\" option.\n     - Improved findtext dialog placement too!\n   * Implemented \"ALIGN = {LEFT|RIGHT|CENTER}\" for <table>, and\n     \"ALIGN = {LEFT|RIGHT|CENTER|JUSTIFY}\" for <tr>.\n   * Implemented character alignment, applied it on ALIGN=CHAR and CHAR for\n     <tr>, <td> and <th>.\n     - New widget DwTableCell.\n     - Some smaller changes in DwAlignedPage and DwPage (virtual word_wrap,\n       ignore_line1_offset_sometimes).\n   * Implemented vertical alignment of table cells.\n     - Changed behavior of Dw_page_size_allocate.\n     - Applied it on \"VALIGN={TOP|BOTTOM|MIDDLE|BASELINE}\" for <tr>, <td> and\n       <th>.\n     - Fixed splash screen.\n   * Set the height of <BR>'s in non-empty lines to zero.\n   * Moved some code from html.c to a_Dw_page_change_link_color.\n   * Made bullets size depending on the font size.\n   * Fixed too wide widgets in lists (e.g. nested lists).\n   Patches: Sebastian Geerken\n - * Added support for <input type=image...> (BUG#313)\n   Patch: Madis Janson, Sebastian Geerken, Jorge Arellano\n - * Made a better EAGAIN handler, and enabled FreeIOVec operation in IOWrite.\n   Patch: Jorge Arellano, Livio Baldini\n - * Fixed include directives for config.h\n   Patch: Jorge Arellano, Claude Marinier\n - * Made lots of minor cleanups.\n   Patches: Lex Hider, Jorge Arellano, Rudmer van Dijk\n - * Added a simple command line interface, and enabled some options (BUG#372).\n   * Added full-window option in command line and dillorc.\n   * Added an option to set offline URLs from CLI.\n   * Made dillo embeddable into other GTK applications.\n   Patches: Jorge Arellano, Melvin Hadasht\n - * Made drafts for dillo plugins protocol (dpi1)\n   Work: Jorge Arellano, Eric Gaudet\n - * Avoided a file lock when cookiesrc disables cookies (BUG#358).\n   * Fixed scroll-jumping between widgets when pressing Up&Dn arrows.\n   * Added a tiny warning/handler for meta refresh.\n   * Concomitant Control Chain (CCC):\n     - Extended the theory to allow bidirectional message passing.\n     - Renewed the API.\n     - Improved the debugging code.\n     - Redesigned the old CCCs, and made a new one for plugins (dpi).\n     - Reimplemented dillo's core with the new chains.\n   * Input/Output engine (IO):\n     - Extended the functionallity with a threaded operation that\n       allows buffered writes of small chunks on the same FD.\n     - Created a new IO API, and adapted dillo to it.\n   * Used the new CCC and IO to implement dillo plugins! (dpi).\n   * Implemented the internal support for a bookmarks dpi.\n   * Wrote a dpi-program for bookmarks.\n   * Created capi.c, a meta module for cache.c.\n   * Restructured Html_write so custom HTML can be inserted.\n   * Set BackSpace and Shift+BackSpace to work as Back/Forward buttons.\n   * Set the escape key as a dialog closing shortcut.\n   * Removed a segfault in find text with a string of spaces (BUG#393)\n   * Added wrappers/whitespace filtering for pasted/typed/CLI URLs. (RFC-2396)\n   * Added an HTML warning message for illegal characters inside URLs.\n   * Made dpi communication go through unix domain sockets.\n   * Enabled dillo to launch the bookmarks plugin!\n   * Made some cleanups in IO/.\n   Patches: Jorge Arellano\n\n\ndillo-0.6.6 [May 30, 2002]\n\n - * Added a few canonical casts to fix some obvious 64bit issues.\n   Patch: pvalchev\n - * Fixed a bug with cookies path parsing.\n   * Fixed persistent-cookies obliteration (BUG#312, BUG#314)\n   * Set max 20 persistent cookies for each domain.\n   Patches: Jrgen Viksell\n - * Switched flock to lockf.\n   Patch: Andreas Schweitzer\n - * Made a little bugfix in doc/Makefile.am.\n   Patch: Grigory Bakunov\n - * Removed the < 256 hostname length restraint from http queries.\n   * Made a date-parser that copes with three HTTP date-syntaxes (BUG#335)\n   * Made the HTML parser a bit more robust with bad HTML (BUG#325, BUG#326)\n   Patches: Jorge Arellano\n\n\ndillo-0.6.5 [Apr 26, 2002]\n\n - * Improved a bit table rendering speed.\n   Patch: Mark Schreiber\n - * Extended Dw crossing events.\n   Patch: Sebastian Geerken\n - * Added code to autoresize the \"View source\" window (BUG#198).\n   Patch: Andreas Schweitzer\n - * Improved *BSD detection code at './configure' time.\n   Patch: Andreas Schweitzer, Jorge Arellano\n - * Added a (pthread_t) cast in dns.c\n   * Fixed a problem with #fragment hash-lookup (in anchors_table).\n   * Added code to install/test usr/local/etc/dillorc (BUG#287)\n   * Added control-character filtering for pasted/typed URLs.\n   * Replaced the old cache list with a hash table!\n   Patches: Livio Baldini\n - * Fixed a momentous memory leak in png decoding.\n   * Fixed a segfault source in GIF colormap handling.\n   Patch: Livio Baldini, Jorge Arellano\n - * Added fontname selection to dillorc.\n   Patch: Arvind Narayanan\n - * Removed a segfault source under G_IO_ERR conditions in IO.c.\n   Patch: Madis Janson\n - * Removed a wild deallocation chance in klist.c\n   Patch: Pekka Lampila\n - * Fixed saving of pages that result from POST.\n   Patch: Nikita Borodikhin\n - * Fixed a tiny bug with dillorc parsing on certain locales (BUG#301)\n   Patch: Lars Clausen, Jorge Arellano\n - * Added support for cookies! RFC-2965 (BUG#82)\n   Patch: Jrgen Viksell, Lars Clausen, Jorge Arellano\n - * Added code to detect redirect-loops (BUG#260)\n   Patch: Jorge Arellano, Chet Murthy\n - * Added support for missing Content-Type in HTTP headers (BUG#216)\n   * Added support for a bare '>' inside attribute values (BUG#306)\n   Patch: Jorge Arellano, Andreas Schweitzer\n - * Allowed enter to submit forms when there's a single text entry.\n   * Added 'generate_submit' and 'enterpress_forces_submit' to dillorc.\n   Patch: Jorge Arellano, Mark Schreiber.\n - * Added support for rendering adjacent <BR>, Tabs in <PRE>, and linebreak\n     handling (BUG#244, BUG#179, BUG#291).\n   Patch: Jorge Arellano, Mark Schreiber, Sebastian Geerken.\n - * Switched a_List_* methods to three parameters (and wiped BUG#286)\n   * Fixed two little bugs within url.c (BUG#294)\n   * Created an API for nav_stack usage (a handy cleanup).\n   * Set the attribute parser to trim leading and trailing white space.\n   * Fixed a problem with NULL requests to the dns (BUG#296).\n   * Added Tru64(tm) detection code at './configure' time.\n   * Fixed the parser to skip <style> and <script> contents (BUG#316).\n   * Bound the space key to PgDn, and 'b' | 'B' to PgUp.\n   * Allowed 'query' and 'fragment' in POST submitions (BUG#284).\n   * Changed the url module API (the URL_* macros), and updated the calling\n     modules, removing several potential bugs at the same time --toilsome.\n   Patches: Jorge Arellano\n\n\ndillo-0.6.4 [Jan 29, 2002]\n\n - * Implemented remembering of page-scrolling-position! (BUG#219)\n   Patch: Jorge Arellano, Livio Baldini\n - * Moved jpeg's include directory from CFLAGS to CPPFLAGS in configure.in\n   Patch: John L. Utz, Lionel Ulmer\n - * Made a standarization cleanup to every *.h\n   * Cleaned some casts to use the GPOINTER_TO_INT and GINT_TO_POINTER macros.\n   * Added the 'static' qualifier to some module-internal variables.\n   * Added the 'static' qualifier to module-internal functions!\n   Patches: Jrgen Viksell\n - * New widget DwAlignedPage for alignment of vertical arrays.\n     - New widget DwListItem for nicer list items (based on some extensions\n       of DwPage) BUG#271.\n   * Implemented text alignments (except CHAR).\n     - Extension of DwStyle and DwPage.\n     - Applied it on \"ALIGN = {LEFT|RIGHT|CENTER}\" for <hr>, and\n       \"ALIGN = {LEFT|RIGHT|CENTER|JUSTIFY}\" for <p>, <hN>, <div>, <td> and\n       <th>. Implemented <center> --BUGs #215, #189.\n   * Small change in DwPageWord (space_style), fixes problems with spaces and\n     underlining (BUG#278).\n   Patches: Sebastian Geerken\n - * Added 'force_visited_colors' to dillorc. It asserts a different color\n   on visited links, regardless of the author's setting.\n   Patch: Jorge Arellano, Sebastian Geerken\n - * Updated and improved several #include directives inside *.c\n   * Added history.c for linear history and scroll-position tracking.\n     Now the navigation-stack references linear history and nav-expect\n     holds a DilloUrl (history.c provides an API).\n   * Fixed a rare data-integrity race-condition with popups (BUG#225)\n   * Made small icons a bit narrower.\n   * Fixed a problem with image-maps handling code (BUG#277)\n   * Added support for several domains in dillorc's 'no_proxy' variable.\n   * Fixed a small boundary-bug in named-colors parsing.\n   * Implemented IOs validity-test with klist (avoids a rare segfault source).\n   Patches: Jorge Arellano\n\n\ndillo-0.6.3 [Dec 23, 2001]\n\n - * Removed a_Dw_widget_set_usize.\n   * Removed *_indent in DwStyle, this is now done by nested widgets.\n   * List items are now single widgets, this fixes bug #78.\n   * Extended queue_resize and related code, removed fast resizing.\n     - Applied these changes on DwPage (many changes!).\n   * Changes in requisition of DwPage.\n   * Added a nice indenter to the pagemarks! (\"Jump to...\" menu).\n   Patches: Sebastian Geerken\n - * Reworked the dicache to use a hash table and use image versions.\n   * Wiped some dicache glitches, and added a dillorc option turn it off!\n     (reducing memory usage significatively).\n   Patches: Livio Baldini\n - * Added support for OSes that use a slightly different 'struct sockaddr'.\n   Patch: Johan Danielsson\n - * Removed a cache leak when reloading (BUG#257).\n   Patch: Livio Baldini, Jorge Arellano\n - * Added full-screen mode! (left double-click toggles it).\n   Patch: Jorge Arellano, Sebastian Geerken\n - * Extended interface customization options in dillorc (a must for iPAQ).\n   Patch: Jorge Arellano, Sam Engstrm\n - * Rewrote the whole tag-parsing code with a new scheme (single pass FSM!)\n     (BUG#190, BUG#197, BUG#207, BUG#228, BUG#239) --Big work here.\n   Patch: Jorge Arellano, Jrgen Viksell\n - * Set form encoding to escape everything but alphanumeric and -_.* (BUG#236)\n   * Rewrote Html_tag_open_input.\n   * Extended BACK and FWD key shortcuts to: {ALT | MOD*} + {, | .}    :-)\n   * Fixed URI fragment parsing (BUG#247).\n   * Centered FindText and OpenUrl dialog windows.\n   * Structured dillorc (now it's more readable! ;)\n   * Added a dillorc option to force transient_dialogs.\n   * Fixed a subtle bug with HTTP end-to-end reload (BUG#234).\n   * Fixed form submition when action has <query> or <fragment> (BUG#255)\n   * Added fast URL resolving methods! (96% rfc2396 compliant by now) BUG#256\n   * Switched form-urlencoded CR to be sent as CR LF pair (BUG#266).\n   * Fixed leaving open FDs when the socket connection fails (BUG#268).\n   Patches: Jorge Arellano\n\n\ndillo-0.6.2 [Oct 17, 2001]\n\n - * Added code to parse away <?...> tags (BUG#203).\n   Patch: Sebastian Geerken\n - * Made an explicit ISO8859-1 requirement in font loading (BUG#193).\n   Patch: Karsten M. Self\n - * Added a temporary handler for frames! (lynx/w3m like).\n   Patch: Livio Baldini\n - * Added gtk_set_locale to dillo's init sequence (BUG#173).\n   Patch: Eric Gaudet, Martynas Jocius\n - * Added support for <big> and <small> tags (BUG#221).\n   Patch: Livio Baldini, Jorge Arellano\n - * Added back and forward history popup menus! (BUG#227)\n   Patch: Jorge Arellano, Eric Gaudet, Olaf Dietsche\n - * Removed anchors from to-proxy queries (also added some checks, BUG#210).\n   * Removed a leak in url.c\n   * Fixed a bug with command-line HTML files that reference images (BUG#217).\n   * Improved status-bar messages a bit, modified toolbar pixmaps and\n     reduced the number of a_Url_dup calls.\n   * Set Ctrl-Q to close window and Alt-Q to quit.\n   * Devised an abstract model for parsing, wrote it into HtmlParser.txt and\n     made dillo compliant with it!\n   * Fixed CR/LF entities parsing inside <PRE> (BUG#188)\n   * Added an error message for unsupported protocols (BUG#226)\n   * Removed some warnings detected with different gcc versions.\n   Patches: Jorge Arellano\n\n\ndillo-0.6.1 [Sep 13, 2001]\n\n - * Changed calculation of shaded colors.\n   * Eliminated redundant code when drawing background colors.\n   * Fixed a bug in DwStyle drawing functions.\n   * Fixed a bug in Dw_page_calc_widget_size.\n   * Some changes for <hr> (also BUG#168).\n   * Added <tr> backgrounds.\n   Patches: Sebastian Geerken\n - * Added support for hexadecimal character references, as &#xA1; (BUG#183)\n   Patch: Liam Quinn\n - * Replaced atoi(3) calls with strtol(3).\n   * Made path comparison case sensitive in a_Url_cmp.\n   Patches: Livio Baldini\n - * Added a tiny handler for <DIV>\n   Patch: Robert J. Thomson\n - * Fixed a segfault source in color parsing, and extended it a bit.\n   Patch: Scott Cooper, Jorge Arellano\n - * Removed a leak with the DilloImage structure (when image is not found).\n   * Fixed (and made faster) Url_str_resolve_relative (BUG#194)\n   Patch: Jorge Arellano, Livio Baldini\n - * Added parsing support for %HexHex escape sequences in file URIs\n   Patch: Jorge Arellano, Livio Baldini, Agustn Ferrn :)\n - * Implemented Ctrl-W (close window) (BUG#87)\n   Patch: Jorge Arellano, Martynas Jocius\n - * Fixed a segfault when dillo cannot access ~/.dillo for some reason.\n   Patch: Jorge Arellano, Amit Vainsencher\n - * Fixed the segfault from untrue Content-Length in HTTP header (BUG#187)\n   * Fixed closing an active browser window from the window manager (bug#91)\n   * Eliminated anchors from HTTP queries (BUG#195)\n   * Fixed the repeated reload segfault (BUG#69)\n   * Updated some docs in doc/ dir.\n   * Added a keyed-list ADT (klist.[ch])\n   * Removed a segfault source in dns.c.\n   * Massive changes in Cache module: redesigned the external and internal API,\n     implemented new methods, changed several algorithms, removed transitory\n     and obsoleted code, removed a segfault source and improved CCC operations.\n   * Changes in Http module: extended error handling, improved abort sequences,\n     and added code that's aware of race conditions (based on klist ADT).\n   * Uniformed CCC start operation in IO, http and cache modules.\n   Patches: Jorge Arellano\n\n\ndillo-0.6.0 [July 31, 2001]\n\n - * Fixed a bunch of memory leaks!\n   * Fixed links on pages with only one line, tuned text-entries size and\n     fixed the HTTP header problem (BUG#180)\n   Patches: Jrgen Viksell\n - * Improved dialogs handling: find_text, view_source, open_url, open_file,\n     save_link and save_page (also removed a leak here).\n   Patches: Jorge Arellano, Jrgen Viksell\n - * Modified GtkDwScrolledWindow and GtkDwViewport, now scrollbars work much\n     better. This also fixes of the wrong viewport length (BUG#137).\n   * Implemented tables! (incomplete)\n     - Changes in Dw: DwTable and DwWidget::get_extremes.\n     - html.c: extended DilloHtmlState, added code for table parsing, moved\n       some attributes from DwPage into the HTML linkblock.\n   * Restructured code for image maps (works now with tables).\n   * Removed \"alt\" attribute from <a> tag (no standard).\n   * Fixed a bug in a_Url_dup.\n   * Extended Dw events: leave_notify_event is now called for more widgets.\n   * Extended DwPage and DwImage signal \"link_entered\".\n   * Extended DwStyle by CSS-style boxes, background colors and border_spacing:\n     - Implemented borders around image links (BUG#169).\n   * Fixed the wrong PNG background? (BUG#172)\n   * Corrected handling of styles by the html parser.\n   * Added alternative, \"fast\" resizing method.\n   * Added a simple possibility to scroll long option menus (BUG#158)\n   * Added backing pixmap, this prevents flickering (BUG#174).\n   * Changes and extensions in handling lenghts, see doc/DwStyle.txt.\n   * Added option \"limit_text_width\".\n   Patches: Sebastian Geerken\n - * Added nowrap attribute to DwStyle, and applied it to <pre> (BUG#134),\n     <td> and <th>.\n   Patch: Jrgen Viksell, Sebastian Geerken\n - * Added a_List_resize to list.h methods.\n   * Added debug.h to standarize debugging messages.\n   Patches: Sebastian Geerken, Jorge Arellano\n - * Added file selection while saving pages or links.\n   Patch: Livio Baldini\n - * Added a few 'const', a missing header and some strength reductions.\n   Patch: Aaron Lehmann\n - * Made dillo to also check '/etc/dillorc' for options.\n   Patch: Eduardo Marcel Maan, Jorge Arellano\n - * Made a help page, and linked it to 'about:help' (BUG#72)\n   Patch: Jorge Arellano, Kristian A. Rink\n - * Added an \"alt\" camp to DilloUrl\n   * Fixed the linkblock memory-leak (BUG#163)\n   * Fixed local file loading from the command line (BUG#164)\n   * Fixed server-side image maps support (BUG#165)\n   * Added code for accel-keys on toolbar buttons\n   * Fixed the segfault with unconnected servers (BUG#170)\n   * Fixed the open HTTP-sockets problem (BUG#171)\n   * Reimplemented the low-level file descriptor handling with GIOChannels\n     (and dillo became even faster!)\n   * Made reload-button to request an end-to-end reload (BUG#64)\n   * Fixed the multiple-POST problem, and added a confirmation dialog (BUG#175)\n   * Finished fixing the repeated link-click problem (BUG#74)\n   * Misc: rewrote the 'about:splash' method, tuned DwPage for minimal\n     memory usage, improved a_Color_parse and Html_read_coords, cleaned-up\n     popup-menus and linkblock initialization, eliminated a lock-source in\n     Html_parse_length.\n   * Added DEBUG_HTML_MSG macro for invalid HTML messages.\n   * Fixed the nav-stack (and multiple #anchors) problem (BUG#177)\n   * Added code to avoid segfaults with unhandled MIME types.\n   * Fixed dns.c from solving the same address on different channels (BUG#178)\n   * Improved error handling and extended the CCC scope! (mainly HTTP).\n   * Fixed a Dw-leak that was affecting: hr, bullets, images, tables (&pages)!\n   * Made several cleanups and added/fixed comments in various places.\n   * Reimplemented find-text with a faster algorithm and extended semantics!!\n   * Fixed some oddities with our autoconf stuff.\n   Patches: Jorge Arellano\n\n\ndillo-0.5.1 [May 30, 2001]\n\n - * Designed a new URL handling scheme, and integrated it throughout the code!\n   Patch: Livio Baldini, Jorge Arellano\n - * Removed a significative memory leak in dw_page.\n   * Added support for EAGAIN in IO.c\n   Patches: Livio Baldini\n - * Removed 6 memory leaks! (of varying significance)\n   Patches: Jrgen Viksell\n - * Fixed a bug in DwPage (BUG#162, crash when clicking on links).\n   * Removed a_Dw_gtk_viewport_queue_anchor and related code (becomes obsolete\n     with the new URL handling scheme).\n   * Speed-optimized key moving in GtkDwScrolledFrame (no more blocking).\n   * Fixed two memory leaks, in Dw_style_color_remove, and\n     Dw_style_font_remove.\n   Patches: Sebastian Geerken\n - * Implemented the splash screen with \"about:\" (No more splash-file saving!)\n   * Set all pthreads to run in detached state.\n   * Reworked dillo's interface so now there're six options; available by\n     changing 'panel_size' and the new 'small_icons' in dillorc.\n   * Removed a minor leak in dns.c and a wild-deallocation source.\n   Patches: Jorge Arellano\n\n\ndillo-0.5.0 [May 10, 2001]\n\n - * Implemented <IMG> ALT as tooltip.\n   * Fixed bug #135 (incorrect update of statusbar when leaving \"ismap\" img).\n   Patches: Livio Baldini, Sebastian Geerken\n - * Completed image scaling (BUG#75).\n   Patch: Sebastian Geerken, Jorge Arellano\n - * Fixed proxy support (BUG#150).\n   Patch: Livio Baldini\n - * Fixed two bugs in the Dw event handling.\n   * Fixed bugs in DwEmbedGtk and GtkDwViewport: idle functions are now\n     removed properly.\n   * Fixed bug in DwEmbedGtk (added call of parent_class->destroy).\n   * Moved DwPageAttr into a new submodule (DwStyle).\n     - Applied DwStyle to DwBullet (BUG#146).\n     - Implemented immediate changing of link color provisionally (BUG#152).\n   * Fixed positioning of headers (half of BUG#118).\n   * Fixed rendering of <b><i> and <i><b> (BUG#145).\n   * Fixed unrecognized dillorc text_color setting (BUG#151).\n   Patches: Sebastian Geerken\n - * Changed word/line structure of DwPage\n   * Improved FORM sending (standar name/value submits) and processing;\n     added READONLY, SIZE, MAXLENGTH attributes, type=BUTTON and some cleanups\n   * Fixed VERBATIM parsing mode (BUG#130)\n   * Fixed a bug in calculating the page width (BUG#136)\n   Patches: Jrgen Viksell\n - * Added a dillorc option to set the location entry within the menu bar.\n   Patch: Eric Gaudet\n - * Integrated some modifications to ease compiling on GNU Darwin.\n   * Added support for leading whitespaces in HREF (BUG#120)\n   * Fixed anchor's hash_table and a few more quirks (were warnings on Alpha)\n   * Fixed entities parsing in URI attributes (BUG#114)\n   * Fixed stop button's sensitivity on plain files (BUG#142)\n   * Made filesize more accurate on directory listings (BUG#143)\n   * Introduced the new Concomitant Control Chain (CCC) design!\n     - All the way in the quering branch\n     - Halfway in the answering branch\n     - Introduced more error handling and status messages\n     - Started implementing error control using the CCC!\n     - Fixed too much caching (BUG#84)\n     - Fixed a CPU hog error condition (BUG#144)\n     - Eliminated the segfault from outdated dns answers (BUG#140)\n     - Fixed repeated Back (faster than rendering) segfault.\n   * Cleaned the header include files\n   * Incremented the valid-charset for dillorc identifiers (BUG#149)\n   * Added support for unterminated quotation of attribute values (BUG#155)\n   Patches: Jorge Arellano\n\n\ndillo-0.4.0 [March 3, 2001]\n\n - * Rewrote most of the Dw module from scratch:\n    - Page widget: ported, added support for relative sizes of widgets, and\n      changed behaviour for pressing button 2 on a link.\n    - Removed the now unnecessary event boxes for check and radio buttons.\n    - Modified the code outside to use new Dw.\n   * Started to implement relative sizes for images (in html.c)\n   * Implemented attributes WIDTH, SIZE and NOSHADE of the <hr> tag.\n   * Added focus adjustment for selection lists (<SELECT>)\n   * Implemented TAB, Shift+TAB navigation in FORMS (BUG#86)\n   Patches: Sebastian Geerken\n - * Included a scaling font_factor into dillorc!\n   Patch: Bruno Widmann\n - * Fixed bugs #125 and #129 (menu item focus and radio reset in forms)\n   Patch: Jrgen Viksell\n - * Added code to ignore anything inside STYLE tags.\n   Patch: Mark McLoughlin\n - * Implemented image rendering based on GdkRGB and DwImage!\n   * Fixed 4 bit color planes support, cleaned the image code,\n     removed a few leaks and added documentation (Images.txt).\n   * Ported every patch from 0.3.2 to 0.4.0\n   Patches: Jorge Arellano\n\n\ndillo-0.3.2 [February 22, 2001]\n\n - * Added the option to use oblique font instead of italic (dillorc)\n   Patch: Eric Gaudet, Sebastian Geerken, Jorge Arellano\n - * Changed Dw_page_find_line_index to use a binary search\n   Patch: Eric Gaudet, Jorge Arellano\n - * Added a visual hint for visited links (BUG#117)\n   * Repaired the dillorc parser to skip unknown symbols (BUG#119)\n   Patch: Eric Gaudet\n - * Fixed the segfault for controls outside FORM and SELECT elements (BUG#121)\n   Patch: Eric Gaudet, Jrgen Viksell\n - * Added support for SUB and SUP tags (BUG#115)\n   Patch: Jrgen Viksell\n - * Added a geometry directive to dillorc (sets initial browser size)\n   Patch: Livio Baldini, Jorge Arellano\n - * Fixed bookmarks loading in new browser windows (BUG#110)\n   * Included a workaround for BUG#71\n   Patch: Livio Baldini\n - * Fixed a CPU hog when clicking ftp URLs (BUG#123)\n   * Set a 64 bytes threshold on pagemark headers\n   Patch: Jorge Arellano\n - * Added check for negative image sizes.\n   Patch: Livio Baldini, Sebastian Geerken\n\n\ndillo-0.3.1 [December 22, 2000]\n\n - * Implemented basic Find-text functionality\n   Patch: Sam Dennis, Sebastian Geerken, Allan Clark and Jorge Arellano :-)\n - * Implemented \"Pagemarks\" (Kind of a headings-based page index!)\n   Patch: Sebastian Geerken and Eric Gaudet\n - * Fixed nested-lists numbering, and added support for \"type\" (BUG#76)\n   * Added support for image maps, both usemap and ismap! (BUG#27)\n   * Set \"on\" as default value for check boxes\n   Patch: Eric Gaudet, Jorge Arellano\n - * Added \"Copy link location\" to the link menu\n   Patch: Eric Gaudet\n - * Removed redundant functions from misc.c\n   * Added support for BASE, CODE, DFN, KBD, SAMP and VAR tags (BUG#106)\n   * Added support for TAB characters in plain text files (BUG#112)\n   Patches: Jrgen Viksell, Jorge Arellano\n - * Fixed a_Url_squeeze (BUG#100)\n   Patch: Livio Baldini, Jorge Arellano\n - * Added gamma support and basic transparency for PNG images (BUG#60)\n   * Moved menu_popup into the 'bw' structure (BUG#96)\n   * Fixed the gif decoder to get image size from the right place (BUG#98)\n   * Made the new browser window size the same as the parent (BUG#55)\n   Patch: Livio Baldini\n - * Added support for ISINDEX method (BUG#15)\n   Patch: Sam Dennis\n - * Added support for bare '<' character parsing\n   * Removed every sign-conflict warnings given by gcc with '-W -Wall'\n   * Fixed several identation problems (rendering)\n   * Implemented \"Save link as\" (link menu)\n   * Removed the subtle bug that used to segfault when deleting and processing\n     queue clients at the same time (BUG#111).\n   * + Some comments, cleanups, size reductions, minor optimizations etc.\n   Patches: Jorge Arellano Cid\n\n\ndillo-0.3.0 [November 13, 2000]\n(Lots of patches are pending!)\n\n - * Added support for <strike>, <s>, <del> and <u> tags.\n   Patch: Jrgen Viksell\n - * Fixed a bug in #anchors code\n   Patch: Sebastian Geerken\n - * Parsed text between script tags, out of the rendering part.\n   * Added support for decimal entities that start with 0.\n   * Added some comments to html.c\n   Patches: Sean 'Shaleh' Perry\n - * Added support for corrupted png images (avoids segfaults!)\n   Patch: Eric Gaudet, Jorge Arellano\n - * Fixed empty title bookmarking (BUG#85 and #88)\n   Patch: Livio\n - * Fixed view-source to take its URL from the right place.\n   Patch: Sam Dennis\n - * Added font support for the compaq iPaq linux distribution.\n   Patch: Eric Christianson\n - * Fixed spaced attribute parsing (BUG#79).\n   * Fixed concurrent save and downloading!\n   * Added alpha support for external (simple) plugins.\n ? * Added a workaround (maybe a bug fix) for BUG#77 (No segfault).\n   * Introduced a new design layer between the IO and Dw:\n    - The imgsink stuff was completely removed.\n    - The dicache was rewritten from scratch and integrated\n      into the normal cache.\n    - A single client queue is being used for both caches.\n    - The file descriptors were replaced by cache keys that serve\n      as connection handlers.\n    - The image data structure and related sources got changed.\n    - Every decoder (png, gif, jpeg) was adapted to the new scheme.\n   * Fixed the file-images caching problem and the associated memory-leaks.\n   * Improved progress bar accuracy for images.\n   * Added progress bar functionality for plain text (+comments +cleanups)\n   * Fixed the right-click-over-plain-text segfault (BUG#80).\n   * Started improving the right-mouse-button menus.\n   Patches: Jorge Arellano Cid\n\n\ndillo-0.2.4 [August 21, 2000]\n\n - * Fixed the white square bug with PNG images (BUG #4)\n   Patch: Luca Rota\n - * Added support for #anchors! (BUG #10)\n   * Added support for resolving relative #anchors (BUG #65)\n   Patches: Sebastian Geerken\n - * Fixed a segfault-source that produced BUG #61.\n   * Made several cleanups and standarizations in html.c\n   * Extended entity-parsing scope, and the list of supported entities.\n   * Rearranged TagInfo data into a new structure.\n   * Added the base for refresh support in META tags.\n   Patches: Sean 'Shaleh' Perry\n - * Added support for TEXTAREA tags!\n   Patch: Jrgen Viksell\n - * Improved and fixed Html_parse_entities.\n   * Reimplemented the Stash buffer with a GString.\n   * Fixed a bug with \\r\\n-terminated HTML lines.\n   * Added redirection support for relative URLs (BUG #73).\n   * Added some comments and minor fixes to patches.\n   Patches: Jorge Arellano Cid\n - * Linked \"open link in new window\" to mouse button #2 (#3 also works)\n   Patch: Eric Gaudet\n\n\ndillo-0.2.3 [August 4, 2000]\n\n - * Removed \"search.h\" include in http.c (freeBSD compatibility).\n   Patch: Kurt J. Lidl\n - * Removed several memory leaks that were sprinkled through the code.\n   Patches: Jrgen Viksell\n - * Fixed a segfault crash when hitting PgDn in the URL box (BUG #54).\n   * Removed a segfault source in commands.c\n   * Made some minor fixes to Dw and added more comments to the code.\n   * Made changes in dw_gtk_view.c, and fixed the rendering problem that\n     arise when changing from a scrolled page into another (BUG #58).\n   * Changes in hruler dynamic resize --not finished though.\n   * Removed a floating point exception bug in image handling code (image.c)\n   * Dramatically improved rendering speed!!! Most notably long HTML pages\n     with lots of links; Improvement ranges from 2 to 5 times faster! (aprox.)\n   * Fixed misplaced rendering of small pages (BUG #35)\n   * Fixed the bookmark bug with empty title strings (BUG #57, #67)\n   * Completed support for \"\\r\", \"\\n\" and \"\\r\\n\" in PRE tags.\n   * Fixed text rendering between multiple selection boxes (BUG #56)\n   * Added several minor enhancements (comments, formatting, speed, etc)\n   * Added extensive documentation! (IO.txt, DilloWidget.txt and Dw.txt)\n   Patches: Jorge Arellano Cid\n\n\ndillo-0.2.2 [July 9, 2000]\n\n - * Added a gtk_window_set_wmclass to all windows to prevent dialogs\n     from having the same size as the main window. (Ex: with Sawfish)\n   * Made some width and height changes to the SELECT-stuff\n   * Added \"submit\" to submit buttons without a value.\n   Patches: Jrgen Viksell\n - * Fixed a segfault when calling \"about:\" method\n   Patch: Dominic Wong\n - * Added an option to force dillorc-defined colors (Try it with slashdot!)\n   * Fixed display of encoded-URL-links on the status bar\n   Patches: Adam Sampson\n - * Removed several compiler dependencies\n     (detected with lcc on a 64 bit machine)\n   * Modified mime.c and Url.c to use list.h, and eliminated hdlr.c\n   * Standarized unsigned types to glib all around the code\n   * Added some includes for libc5 compatibility\n   * Modified IO_callback to avoid a CPU-hog (it happened in some systems).\n   * Fixed a bug that added a trailing ampersand to GET and POST queries.\n   * FIxed attribute parsing. It had nasty side effects; as providing\n     wrong attribute values to POST and GET methods.\n   * Joined Url.c and url.c into a single module.\n   * Reimplemented URL resolving functions.\n   * Implemented a new parser for \"file:\" URLs  (Try \"file:\" & \"file:.\").\n   * Removed child_linkblock and changed the HTML stack handling\n     (both changes result in a simpler, easier to understand code).\n   * Modified and removed a segfault source in Html_lb_new.\n   * Modified forms handling to be more tolerant with messy HTML.\n   * Linked \"image/pjpeg\" in MIME types (progressive jpeg)\n   * Fixed form submittion when there's no submit button (bug #49)\n     Now dillo can search on freshmeat, altavista, etc!\n   Patches: Jorge Arellano Cid\n\n\ndillo-0.2.1 [June 17, 2000]\n\n - * Modified the pixmaps for better interface perception ;)\n   * Modified Dw_gtk_view_adjustment_value_changed to update the visible\n     rectangle even though the widget is not realized; it seems to work!\n   * Implemented the horizontal ruler as a Dw  --dw_hruler.[ch]\n     Fixed its expose problems (Bug #13). (todo: resizing).\n   * Changed Dw_gtk_progressbar module to \"progressbar\" --naming stuff\n   * Added Content-length in file headers (avoids reallocations)\n   * Modified form submittion and encoding to use dynamic memory allocation\n   * Eliminated a dns.c hack that passed an int as a void* ;)\n   * Updated the documentation with an extensive IO description.\n   Patches: Jorge Arellano Cid\n - * Added some functionality to reload button (not complete yet)\n   Patch: Luca Rota , Jorge Arellano Cid\n - * Fixed hash handling within URL parsing. (Bug #9)\n   Patch: Marcos Ramrez , Jorge Arellano Cid\n\n\ndillo-0.2.0 [June 2, 2000]\n*** THIS IS A HALF-NEW BROWSER ***\n\n - * Finally reimplemented the whole networking process (***BIG changes***)\n        Rewrote from scratch: IO, cache, web, http, socket, ...\n        Modified: gif, png, jpeg, html, nav, plain, ... (Every client)\n     All the querying, retrieving, caching and delivering is NEW!!!\n   * Eliminated CPU-hogging while waiting for a DNS query to complete\n   * Eliminated CPU-hogging when facing redirections\n   * Implemented basic redirection functionality\n   * Eliminated several segmentation fault bugs\n   * Modified autoconf stuff\n   * Modified source-code tree and libraries\n   * Reduced binary size\n   * Eliminated a memory leak in socket connections\n   * Created a new socket connection scheme\n   * Implemented Cache as the main networking abstraction layer\n   * Joined almost every URL-retrieving module into libDio\n   * Set the basis for save-link-as functionality (see save function)\n   * Modified the navigation stack to a cleaner design\n   * Improved status bar messages when connecting\n   * Changed some function names\n   * Created new pixmaps for the toolbar!\n   * Added a \"new\" button near the URL to clear the entry!\n   * Added a_List_remove to list.h\n   * Updated documentation in doc/\n     (README, Cache.txt, store.txt, Dillo.txt, Images.txt and IO.txt)\n   Patches: Jorge Arellano Cid\n - * Added a workaround patch for BUG #35 (page expose problems)\n   Patch: Andrew McPherson\n\n\ndillo-0.1.0 [Mar 30, 2000]\n\n - * Fixed a bug that used to lock hostname queries.\n     ('DNS can't resolve name' mesg.)\n   * Fixed the wrong parent link when browsing directory contents\n   * Changed the file/directory HTML-output-layout\n   * Finally rewrote the whole file.c module :-)\n   * Made Http_query buffer overflow-safe\n   * Commented and cleaned web.c\n   * Changed the licence to GPL. (Raph agreed on that)\n   * Fixed a tag-search bug in html.c; it produced rendering problems.\n   * Fixed a parsing problem with tags that were split on different lines\n   * Fixed the after-tables parsing problem\n   * Added a startup page\n   Patches: Jorge Arellano Cid\n - * Fixed a bug with http queries that sometimes produced infinite loops\n   Patch: Marcos Ramrez\n\n\ndillo-0.0.6 [Mar 9, 2000]\n\n - * Readded an old, wiped-by-mistake, bug fix.\n   * Added preferences settings using a readable config (dillorc)\n   * Added a page-title trimmer facility (39 chars) to bookmarks saving.\n   Patch: Luca Rota\n - * Fixed three memory leaks in bookmarks reading\n   * Added 'Open link in a new window' within the right button pop-up-menu\n   Patch: Sammy Mannaert\n - * Fixed a bug that used to put two slashes on directory file anchors\n   * Actualized plugin.txt to current code base (and a bit of fix)\n   * Changed \"fprintf(stderr...\" to \"g_print(...\"\n   * Improved list.h\n   * Fixed image URLs both for HTTP and local files!\n   * Fixed tag attribute parsing (The trimmed-text-inside-buttons bug)\n   * Wrote several documentation files (placed them in doc/)\n   * Fixed transparent image rendering\n   * Implemented a binary search for HTML tags (just a bit faster)\n   * Small leak fixes and some corrections to http.c\n   * Made style fixes, added function comments and things like that.\n   Patches: Jorge Arellano Cid\n\n\ndillo-0.0.5 [Feb 3, 2000]\n\n - * Added progress bars (to be improved)\n   Patch: James McCollough, Jorge Arellano Cid\n - * Rearranged, changed and commented the decompressed image cache code\n   * Fixed autoconf stuff to include zlib\n   * Added memory deallocating functions to cache, dicache, socket, http & dns\n   * Fixed memory leaks in jpeg.c, png.c and gif.c\n   * Made several changes in dw_page.c and dw_image.c\n   * Introduced 'list.h' source, and standarized the whole code to use it\n   * Fixed image rendering (Fixed algorithms and data structures) BIG CHANGES\n   * Removed some false comments and added true ones (I hope ;)\n   * Made several \"standarizing\" changes all over the code and\n   * some other changes (not worth listing)\n   Patches: Jorge Arellano Cid\n - * Added support for 'text' and 'link' colors inside <BODY> tags\n   * Standarized all memory management to glib functions\n   * Fixed the plugin API to work again (forked)\n   * Removed a warning (remove not implemented for Dw_view and Dw_scroller)\n   * Solved the page-without-title bug in bookmarks.\n   Patches: Luca Rota\n\n\ndillo-0.0.4 [Jan 4, 2000]\n\n - * Removed the test widget\n   * Added a jpeg image decoder error-handler\n   Patches: Sammy Mannaert\n - * Changed some ADTs to glib to be compatible with newer glibc2 systems\n   * Added background color alternative when bg. is white (or not specified)\n   * Improved connecting time status messages\n   Patches: Jorge Arellano Cid\n - * Added background color support.\n   Patch: Luca Rota, James McCollough\n - * Added support for <OL></OL> tags\n   * Added view-source and view-bookmarks functionality\n   * Improved PgUP/PgDown and Up/Down response. (No need to grab focus!)\n   * Fixed openfile gtk run-time warning\n   * Fixed the focus problem with text camps\n   * Fixed the title-linger bug with pages that don't specify title.\n   * Added a preliminary right button menu\n   * Added POST method support\n   Patches: Luca Rota\n - * Added PNG image support.\n   Source Code: Geoff Lane, Patch: Jorge Arellano\n\n\ndillo-0.0.3.tar.gz [Dec 18, 1999]\n\n - * Finished the whole Naming&Coding effort!!!\n   Stage 2 worked by: Luca Rota and Jorge Arellano\n - * Removed all compile time warnings (at least with gcc 2.7.2.3)\n   * Added more documentation inside the code\n   * Removed the '~/.dillo' directory creation bug.\n   * Integrated a patch for menu module\n   * Renamed menus.c to menu.c\n   * And some other minor things...\n   Patches: Jorge Arellano Cid\n\n\ndillo-0.0.2.tar.gz [Dec, 1999  --Does anyone remember the day?]\n\n - * Finished stage one of the naming&coding design (Hey, it's 1.3 Mb of code!)\n   Worked by: Jorge Arellano, Sammy Mannaert, James McCollough and Luca Rota\n - * Removed some bugs and renamed the source files.\n   * Heavily rearranged URL/ an IO/ files for better understanding & debugging\n   * Added more documentation within the sources\n   * Recoded automake stuff\n   * Integrated some queued patches\n   * (And several things that I have no time to write now! :-)\n   Patches: Jorge Arellano Cid\n\n\ndillo-0.0.1.tar.gz [Dec, 1999]\n\n - * Halfway release, amidst stage one of the naming&coding effort.\n   Worked by: Jorge Arellano, Sammy Mannaert, James McCollough and Luca Rota\n\n\ndillo-0.0.0.tar.gz [Dec, 1999]\n\n - * Applied a cleanning patch to menus.[ch]\n   Patch: Sammy Mannaert\n - * Made a threaded DNS scheme (several improvements: now it works with gdb)\n   * Bug fix on TMP_FAILURE_RETRY\n   * Bug fix on links not been followed (Url parsing)\n   * Changed the default pixmaps\n   * Maked automake, autoconf, autoheader, changes\n   * Changed binary name\n   Patches: Jorge Arellano Cid\n\n\n\n"
  },
  {
    "path": "Doxyfile",
    "content": "# Doxyfile 1.8.9.1\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file\n# that follow. The default is UTF-8 which is also the encoding used for all text\n# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv\n# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv\n# for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = Dillo\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       =\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       =\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 8\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines.\n\nALIASES                =\n\n# This tag can be used to specify a number of word-keyword mappings (TCL only).\n# A mapping has the form \"name=value\". For example adding \"class=itcl::class\"\n# will allow you to use the command class in the itcl::class meaning.\n\nTCL_SUBST              =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:\n# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:\n# Fortran. In the later case the parser tries to guess whether the code is fixed\n# or free formatted code, this is the default for Fortran type files), VHDL. For\n# instance to make doxygen treat .inc files as Fortran files (default is PHP),\n# and .f files as C (default is Fortran), use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See http://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = YES\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = YES\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = YES\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = NO\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO, these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# and Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = YES\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = NO\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces.\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  =\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: http://www.gnu.org/software/libiconv) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank the\n# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,\n# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,\n# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,\n# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,\n# *.qsf, *.as and *.js.\n\nFILE_PATTERNS          = *.cc \\\n                         *.hh \\\n                         *.doc\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                = dlib \\\n                         dpi \\\n                         dpid \\\n                         dpip \\\n                         src\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       =\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             = devdoc\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = NO\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# function all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = YES\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = YES\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see http://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the config file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 3\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# http://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: http://developer.apple.com/tools/xcode/), introduced with\n# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html\n# for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          =\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-\n# folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = devdoc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-\n# filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# http://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from http://www.mathjax.org before deployment.\n# The default value is: http://cdn.mathjax.org/mathjax/latest.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = NO\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: http://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when enabling USE_PDFLATEX this option is only used for generating\n# bitmaps for formulas in the HTML output, but not in the Makefile that is\n# written to the output directory.\n# The default file is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4wide\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. To get the times font for\n# instance you can specify\n# EXTRA_PACKAGES=times\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = NO\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = NO\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# http://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's config\n# file, i.e. a series of assignments. You only have to provide replacements,\n# missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's config file. A template extensions file can be generated\n# using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sf.net) file that captures the\n# structure of the code including all documentation. Note that this feature is\n# still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             =\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script\n# interpreter (i.e. the result of 'which perl').\n# The default file (with absolute path) is: /usr/bin/perl.\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. Doxygen will then run the mscgen tool (see:\n# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the\n# documentation. The MSCGEN_PATH tag allows you to specify the directory where\n# the mscgen tool resides. If left empty the tool is assumed to be found in the\n# default search path.\n\nMSCGEN_PATH            =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = YES\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = FreeSans\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = NO\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = NO\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = YES\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = NO\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot.\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif and svg.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "Makefile",
    "content": "include Makefile.options\n\nall: config.h\n\t@echo Making all in lout\n\t@(cd lout; make all)\n\t@echo Making all in dw\n\t@(cd dw; make all)\n\t@echo Making all in dlib\n\t@(cd dlib; make all)\n\t@echo Making all in dpip\n\t@(cd dpip; make all)\n\t@echo Making all in src\n\t@(cd src; make all)\n\t@echo Making all in doc\n\t@(cd doc; make all)\n\t@echo Making all in dist\n\t@(cd dist; make all)\n\t@echo Making all in dpid\n\t@(cd dpid; make all)\n\t@echo Making all in dpi\n\t@(cd dpi; make all)\n\ntest:\n\t@echo Making all in test\n\t@(cd test; make all)\n\nclean:\n\t@echo Cleaning in lout\n\t@(cd lout; make clean)\n\t@echo Cleaning in dw\n\t@(cd dw; make clean)\n\t@echo Cleaning in dlib\n\t@(cd dlib; make clean)\n\t@echo Cleaning in dpip\n\t@(cd dpip; make clean)\n\t@echo Cleaning in src\n\t@(cd src; make clean)\n\t@echo Cleaning in doc\n\t@(cd doc; make clean)\n\t@echo Cleaning in dist\n\t@(cd dist; make clean)\n\t@echo Cleaning in dpid\n\t@(cd dpid; make clean)\n\t@echo Cleaning in dpi\n\t@(cd dpi; make clean)\n\t@echo Cleaning in test\n\t@(cd test; make clean)\n\ninstall: all\n\t@echo Making install in lout\n\t@(cd lout; make install)\n\t@echo Making install in dw\n\t@(cd dw; make install)\n\t@echo Making install in dlib\n\t@(cd dlib; make install)\n\t@echo Making install in dpip\n\t@(cd dpip; make install)\n\t@echo Making install in src\n\t@(cd src; make install)\n\t@echo Making install in doc\n\t@(cd doc; make install)\n\t@echo Making install in dist\n\t@(cd dist; make install)\n\t@echo Making install in dpid\n\t@(cd dpid; make install)\n\t@echo Making install in dpi\n\t@(cd dpi; make install)\n\t#@echo Making install in test\n\t#@(cd test; make install)\n\t./install-sh -c -d \"$(DILLO_BINDIR)\"\n\t$(INSTALL) -c dillo-install-hyphenation \"$(DILLO_BINDIR)\"\n\t./install-sh -c -d \"$(DILLO_ETCDIR)\"\n\t$(INSTALL) -c -m 644 dillorc \"$(DILLO_ETCDIR)\"\n\t$(INSTALL) -c -m 644 bm.txt \"$(DILLO_ETCDIR)\"\n\t$(INSTALL) -c -m 644 style.css \"$(DILLO_ETCDIR)\"\n\t$(INSTALL) -c -m 644 style_reader_mode.css \"$(DILLO_ETCDIR)\"\n\nuninstall:\n\t@echo Making uninstall in lout\n\t@(cd lout; make uninstall)\n\t@echo Making uninstall in dw\n\t@(cd dw; make uninstall)\n\t@echo Making uninstall in dlib\n\t@(cd dlib; make uninstall)\n\t@echo Making uninstall in dpip\n\t@(cd dpip; make uninstall)\n\t@echo Making uninstall in src\n\t@(cd src; make uninstall)\n\t@echo Making uninstall in doc\n\t@(cd doc; make uninstall)\n\t@echo Making uninstall in dist\n\t@(cd dist; make uninstall)\n\t@echo Making uninstall in dpid\n\t@(cd dpid; make uninstall)\n\t@echo Making uninstall in dpi\n\t@(cd dpi; make uninstall)\n\t#@echo Making uninstall in test\n\t#@(cd test; make uninstall)\n\trm -f \"$(DILLO_BINDIR)/dillo-install-hyphenation\"\n\trm -f \"$(DILLO_ETCDIR)/dillorc\"\n\trm -f \"$(DILLO_ETCDIR)/bm.txt\"\n\trm -f \"$(DILLO_ETCDIR)/style.css\"\n\trm -f \"$(DILLO_ETCDIR)/style_reader_mode.css\"\n"
  },
  {
    "path": "Makefile.options",
    "content": "# == SYSTEM BINARIES ==\n\nAR ?= ar\nARFLAGS ?= cru\nRANLIB ?= ranlib\n\nCC ?= gcc\nCXX ?= g++\n\nINSTALL ?= /usr/bin/install\nINSTALL_SH = ../install-sh\n\n# == COMPILATION FLAGS ==\n\nCOMPILE = $(CC) $(INCLUDES) $(CFLAGS)\nLINK = $(CC) $(CFLAGS) $(LDFLAGS) -o $@\n\nCFLAGS ?= -g -O2\nCFLAGS += -DD_DNS_THREADED -D_REENTRANT -DHAVE_CONFIG -D_THREAD_SAFE -Wall -W -Wno-unused-parameter -Waggregate-return -Wl,--no-as-needed\nINCLUDES ?= -I. -I.. -I/usr/local/include\nLDFLAGS ?= -L/usr/local/lib\n\nCXXCOMPILE = $(CXX) $(INCLUDES) $(CXXFLAGS)\nCXXFLAGS = $(CFLAGS) -fno-rtti -fno-exceptions\n\nLIBFLTK_CFLAGS = -I/usr/X11R6/include/freetype2 -pipe -I/usr/X11R6/include -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT\nLIBFLTK_CXXFLAGS = $(LIBFLTK_CFLAGS) -fvisibility-inlines-hidden\nLIBFLTK_LDFLAGS = $(LDFLAGS) -Wl,-rpath,/usr/local/lib -L/usr/X11R6/lib -lfltk -lXcursor -lXfixes -lXext -lXft -lfontconfig -lXinerama -lpthread -lm -lX11 -lXdmcp -lXau\n\nHTTPS_LDFLAGS = -lcrypto -lssl\n\nLIBPNG16_CXXFLAGS = -I/usr/local/include/libpng16\n\nDILLO_LDFLAGS = -ljpeg -L/usr/local/lib -lpng -Wl,-rpath,/usr/local/lib -L/usr/X11R6/lib -lfltk -lXcursor -lXfixes -lXext -lXft -lfontconfig -lXinerama -lpthread -lm -lX11 -lXdmcp -lXau -lz -lX11\n\n# == HTTPS CERTS ==\n\nCA_CERTS_FILE ?= /etc/ssl/cert.pem\nCA_CERTS_DIR ?= /etc/ssl/\n\n# == EXTERNAL TOOLS ==\n\nDOWNLOADER_TOOL = wget\nDOWNLOADER_USER_AGENT_ARG = -U\nDOWNLOADER_CONTINUE_ARG = -c\nDOWNLOADER_LOAD_COOKIES_ARG = --load_cookies\nDOWNLOADER_OUTPUT_FILENAME_ARG = -O\n\nZIP_USE_7Z = 0\nFTP_USE_WGET = 1\n\n# == INSTALL PATHS ==\n\nBINNAME = dillo-plus\n\nPREFIX ?= /usr/local\nLIBDIR = $(PREFIX)/lib/\n\nDILLO_BINDIR = $(PREFIX)/bin/\nDILLO_LIBDIR = $(PREFIX)/lib/$(BINNAME)/\nDILLO_ETCDIR = $(PREFIX)/etc/$(BINNAME)/\n\nDPIDRC_SYS = $(DILLO_ETCDIR)/dpidrc\n\nDOC_PATH = $(PREFIX)/share/doc/$(BINNAME)\nMAN_PATH = $(PREFIX)/share/man/man1\n\nAPP_PATH = $(PREFIX)/share/applications\nICON_PATH = $(PREFIX)/share/icons\n\n"
  },
  {
    "path": "Makefile.options.Linux",
    "content": "# == SYSTEM BINARIES ==\n\nAR ?= ar\nARFLAGS ?= cru\nRANLIB ?= ranlib\n\nCC ?= gcc\nCXX ?= g++\n\nINSTALL ?= /usr/bin/install\nINSTALL_SH = ../install-sh\n\n# == COMPILATION FLAGS ==\n\nCOMPILE = $(CC) $(INCLUDES) $(CFLAGS)\nLINK = $(CC) $(CFLAGS) $(LDFLAGS) -o $@\n\nCFLAGS ?= -g -O2\nCFLAGS += -DD_DNS_THREADED -D_REENTRANT -DHAVE_CONFIG -D_THREAD_SAFE -Wall -W -Wno-unused-parameter -Waggregate-return -Wl,--no-as-needed\nINCLUDES ?= -I. -I.. -I/usr/local/include\nLDFLAGS ?= -L/usr/local/lib\n\nCXXCOMPILE = $(CXX) $(INCLUDES) $(CXXFLAGS)\nCXXFLAGS = $(CFLAGS) -fno-rtti -fno-exceptions\n\nLIBFLTK_CFLAGS = -I/usr/X11R6/include/freetype2 -pipe -I/usr/X11R6/include -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT\nLIBFLTK_CXXFLAGS = $(LIBFLTK_CFLAGS) -fvisibility-inlines-hidden\nLIBFLTK_LDFLAGS = $(LDFLAGS) -Wl,-rpath,/usr/local/lib -L/usr/X11R6/lib -lfltk -lXcursor -lXfixes -lXext -lXft -lfontconfig -lXinerama -lpthread -lm -lX11 -lXdmcp -lXau\n\nHTTPS_LDFLAGS = -lcrypto -lssl\n\nLIBPNG16_CXXFLAGS = -I/usr/local/include/libpng16\n\nDILLO_LDFLAGS = -ljpeg -L/usr/local/lib -lpng -Wl,-rpath,/usr/local/lib -L/usr/X11R6/lib -lfltk -lXcursor -lXfixes -lXext -lXft -lfontconfig -lXinerama -lpthread -lm -lX11 -lXdmcp -lXau -lz -lX11\n\n# == HTTPS CERTS ==\n\nCA_CERTS_FILE ?= /etc/ssl/cert.pem\nCA_CERTS_DIR ?= /etc/ssl/\n\n# == EXTERNAL TOOLS ==\n\nDOWNLOADER_TOOL = wget\nDOWNLOADER_USER_AGENT_ARG = -U\nDOWNLOADER_CONTINUE_ARG = -c\nDOWNLOADER_LOAD_COOKIES_ARG = --load_cookies\nDOWNLOADER_OUTPUT_FILENAME_ARG = -O\n\nZIP_USE_7Z = 0\nFTP_USE_WGET = 1\n\n# == INSTALL PATHS ==\n\nBINNAME = dillo-plus\n\nPREFIX ?= /usr/local\nLIBDIR = $(PREFIX)/lib/\n\nDILLO_BINDIR = $(PREFIX)/bin/\nDILLO_LIBDIR = $(PREFIX)/lib/$(BINNAME)/\nDILLO_ETCDIR = $(PREFIX)/etc/$(BINNAME)/\n\nDPIDRC_SYS = $(DILLO_ETCDIR)/dpidrc\n\nDOC_PATH = $(PREFIX)/share/doc/$(BINNAME)\nMAN_PATH = $(PREFIX)/share/man/man1\n\nAPP_PATH = $(PREFIX)/share/applications\nICON_PATH = $(PREFIX)/share/icons\n\n"
  },
  {
    "path": "Makefile.options.Linux.Wayland",
    "content": "# Provided by @XDream8 via patch\n# Source: https://github.com/crossbowerbt/dillo-plus/issues/15\n\n# Still not complete, but you can start from this file for a wayland port\n\n# == SYSTEM BINARIES ==\n\nAR ?= ar\nARFLAGS ?= cru\nRANLIB ?= ranlib\n\nCC ?= gcc\nCXX ?= g++\n\nINSTALL ?= /usr/bin/install\nINSTALL_SH = ../install-sh\n\n# == COMPILATION FLAGS ==\n\nCOMPILE = $(CC) $(INCLUDES) $(CFLAGS)\nLINK = $(CC) $(CFLAGS) $(LDFLAGS) -o $@\n\nCFLAGS ?= -g -O2\nCFLAGS += -DDISABLE_XEMBED -DD_DNS_THREADED -D_REENTRANT -DHAVE_CONFIG -D_THREAD_SAFE -Wall -W -Wno-unused-parameter -Waggregate-return -Wl,--no-as-needed\nINCLUDES ?= -I. -I.. -I/usr/local/include\nLDFLAGS ?= -L/usr/lib -L/usr/local/lib\n\nCXXCOMPILE = $(CXX) $(INCLUDES) $(CXXFLAGS)\nCXXFLAGS = $(CFLAGS) -fno-rtti -fno-exceptions\n\nLIBFLTK_CFLAGS = -pipe -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT\nLIBFLTK_CXXFLAGS = $(LIBFLTK_CFLAGS) -fvisibility-inlines-hidden\nLIBFLTK_LDFLAGS = $(LDFLAGS) -lfltk -lfontconfig -lpthread -lm\n\nHTTPS_LDFLAGS = -lcrypto -lssl\n\nLIBPNG16_CXXFLAGS = -I/usr/include/libpng16 -I/usr/local/include/libpng16\n\nDILLO_LDFLAGS = -ljpeg -L/usr/lib -L/usr/local/lib -lpng -Wl,-rpath,/usr/lib -Wl,-rpath,/usr/local/lib -lfltk -lfontconfig -lpthread -lm -lz\n\n# == HTTPS CERTS ==\n\nCA_CERTS_FILE ?= /etc/ssl/cert.pem\nCA_CERTS_DIR ?= /etc/ssl/\n\n# == EXTERNAL TOOLS ==\n\nDOWNLOADER_TOOL = wget\nDOWNLOADER_USER_AGENT_ARG = -U\nDOWNLOADER_CONTINUE_ARG = -c\nDOWNLOADER_LOAD_COOKIES_ARG = --load_cookies\nDOWNLOADER_OUTPUT_FILENAME_ARG = -O\n\nZIP_USE_7Z = 0\nFTP_USE_WGET = 1\n\n# == INSTALL PATHS ==\n\nBINNAME = dillo-plus\n\nPREFIX ?= /usr/local\nLIBDIR = $(PREFIX)/lib/\n\nDILLO_BINDIR = $(PREFIX)/bin/\nDILLO_LIBDIR = $(PREFIX)/lib/$(BINNAME)/\nDILLO_ETCDIR = $(PREFIX)/etc/$(BINNAME)/\n\nDPIDRC_SYS = $(DILLO_ETCDIR)/dpidrc\n\nDOC_PATH = $(PREFIX)/share/doc/$(BINNAME)\nMAN_PATH = $(PREFIX)/share/man/man1\n\nAPP_PATH = $(PREFIX)/share/applications\nICON_PATH = $(PREFIX)/share/icons\n\n"
  },
  {
    "path": "Makefile.options.MacOS",
    "content": "# Provided by Timur Ismagilov (@bouncepaw)\n# Tested for MacBook Air M1 2020 running Big Sur\n\n# Original code:\n# https://gist.github.com/bouncepaw/0c3744497d1764abf30f6a515498ddb7\n\n# == SYSTEM BINARIES ==\n\nAR ?= ar\nARFLAGS ?= cru\nRANLIB ?= ranlib\n\nCC ?= gcc\nCXX ?= g++\n\nINSTALL ?= /usr/bin/install\nINSTALL_SH = ../install-sh\n\n# == COMPILATION FLAGS ==\n\nCOMPILE = $(CC) $(INCLUDES) $(CFLAGS)\nLINK = $(CC) $(CFLAGS) $(LDFLAGS) -o $@\n\nCFLAGS ?= -g -O2\nCFLAGS += -DD_DNS_THREADED -D_REENTRANT -DHAVE_CONFIG -D_THREAD_SAFE -Wall -W -Wno-unused-parameter -Waggregate-return\nINCLUDES ?= -I. -I.. -I/usr/local/include -I/opt/homebrew/Cellar/fltk/1.3.8_1/include -I/opt/homebrew/include/\nLDFLAGS ?= -L/opt/homebrew/opt/libiconv/lib -L/opt/homebrew/opt/openssl@3.1/lib -L/opt/homebrew/opt/jpeg/lib -L/usr/local/lib -L/opt/homebrew/Cellar/fltk/1.3.8_1/lib -lfltk -lpthread -framework Cocoa -v\n\nCXXCOMPILE = $(CXX) $(INCLUDES) $(CXXFLAGS)\nCXXFLAGS = $(CFLAGS) -fno-rtti -fno-exceptions\n\nLIBFLTK_CFLAGS = -I/usr/X11R6/include/freetype2 -pipe -I/usr/X11R6/include -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT\nLIBFLTK_CXXFLAGS = $(LIBFLTK_CFLAGS) -fvisibility-inlines-hidden\nLIBFLTK_LDFLAGS = $(LDFLAGS) -Wl,-rpath,/usr/local/lib -L/usr/X11R6/lib -lfltk -lXcursor -lXfixes -lXext -lXft -lfontconfig -lXinerama -lpthread -lm -lX11 -lXdmcp -lXau\n\nHTTPS_LDFLAGS = -L/opt/homebrew/opt/openssl@3.1/lib -lcrypto -lssl\n\nLIBPNG16_CXXFLAGS = -I/usr/local/include/libpng16\n\nDILLO_LDFLAGS = -ljpeg -L/usr/local/lib -lpng -Wl,-rpath,/usr/local/lib -L/usr/X11R6/lib -lfltk -lXcursor -lXfixes -lXext -lXft -lfontconfig -lXinerama -lpthread -lm -lX11 -lXdmcp -lXau -lz  -liconv  -lX11\n\n# == HTTPS CERTS ==\n\nCA_CERTS_FILE ?= /etc/ssl/cert.pem\nCA_CERTS_DIR ?= /etc/ssl/\n\n# == EXTERNAL TOOLS ==\n\nDOWNLOADER_TOOL = wget\nDOWNLOADER_USER_AGENT_ARG = -U\nDOWNLOADER_CONTINUE_ARG = -c\nDOWNLOADER_LOAD_COOKIES_ARG = --load_cookies\nDOWNLOADER_OUTPUT_FILENAME_ARG = -O\n\nZIP_USE_7Z = 0\nFTP_USE_WGET = 1\n\n# == INSTALL PATHS ==\n\nBINNAME = dillo-plus\n\nPREFIX ?= /usr/local\nLIBDIR = $(PREFIX)/lib/\n\nDILLO_BINDIR = $(PREFIX)/bin/\nDILLO_LIBDIR = $(PREFIX)/lib/$(BINNAME)/\nDILLO_ETCDIR = $(PREFIX)/etc/$(BINNAME)/\n\nDPIDRC_SYS = $(DILLO_ETCDIR)/dpidrc\n\nDOC_PATH = $(PREFIX)/share/doc/$(BINNAME)\nMAN_PATH = $(PREFIX)/share/man/man1\n\nAPP_PATH = $(PREFIX)/share/applications\nICON_PATH = $(PREFIX)/share/icons\n\n"
  },
  {
    "path": "Makefile.options.OpenBSD",
    "content": "# == SYSTEM BINARIES ==\n\nAR ?= ar\nARFLAGS ?= cru\nRANLIB ?= ranlib\n\nCC ?= cc\nCXX ?= c++\n\nINSTALL ?= /usr/bin/install\nINSTALL_SH = ../install-sh\n\n# == COMPILATION FLAGS ==\n\nCOMPILE = $(CC) $(INCLUDES) $(CFLAGS)\nLINK = $(CC) $(CFLAGS) $(LDFLAGS) -o $@\n\nCFLAGS ?= -g -O2\nCFLAGS += -DD_DNS_THREADED -D_REENTRANT -DHAVE_CONFIG -D_THREAD_SAFE -Wall -W -Wno-unused-parameter -Waggregate-return -Wl,--no-as-needed\nINCLUDES = -I. -I.. -I/usr/local/include\nLDFLAGS = -L/usr/local/lib\n\nCXXCOMPILE = $(CXX) $(INCLUDES) $(CXXFLAGS)\nCXXFLAGS = $(CFLAGS) -fno-rtti -fno-exceptions\n\nLIBFLTK_CFLAGS = -I/usr/X11R6/include/freetype2 -pipe -I/usr/X11R6/include -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT\nLIBFLTK_CXXFLAGS = $(LIBFLTK_CFLAGS) -fvisibility-inlines-hidden\nLIBFLTK_LDFLAGS = $(LDFLAGS) -Wl,-rpath,/usr/local/lib -L/usr/X11R6/lib -lfltk -lXcursor -lXfixes -lXext -lXft -lfontconfig -lXinerama -lpthread -lm -lX11 -lXdmcp -lXau\n\nHTTPS_LDFLAGS = -lcrypto -lssl\n\nLIBPNG16_CXXFLAGS = -I/usr/local/include/libpng16\n\nDILLO_LDFLAGS = -ljpeg -L/usr/local/lib -lpng -Wl,-rpath,/usr/local/lib -L/usr/X11R6/lib -lfltk -lXcursor -lXfixes -lXext -lXft -lfontconfig -lXinerama -lpthread -lm -lX11 -lXdmcp -lXau -lz  -liconv  -lX11\n\n# == HTTPS CERTS ==\n\nCA_CERTS_FILE ?= /etc/ssl/cert.pem\nCA_CERTS_DIR ?= /etc/ssl/\n\n# == EXTERNAL TOOLS ==\n\nDOWNLOADER_TOOL = ftp\nDOWNLOADER_USER_AGENT_ARG = -U\nDOWNLOADER_CONTINUE_ARG = -C\nDOWNLOADER_LOAD_COOKIES_ARG = -c\nDOWNLOADER_OUTPUT_FILENAME_ARG = -o\n\nZIP_USE_7Z = 1\nFTP_USE_WGET = 0\n\n# == INSTALL PATHS ==\n\nBINNAME = dillo-plus\n\nPREFIX ?= /usr/local\nLIBDIR = $(PREFIX)/lib/\n\nDILLO_BINDIR = $(PREFIX)/bin/\nDILLO_LIBDIR = $(PREFIX)/lib/$(BINNAME)/\nDILLO_ETCDIR = $(PREFIX)/etc/$(BINNAME)/\n\nDPIDRC_SYS = $(DILLO_ETCDIR)/dpidrc\n\nDOC_PATH = $(PREFIX)/share/doc/$(BINNAME)\nMAN_PATH = $(PREFIX)/man/man1\n\nAPP_PATH = $(PREFIX)/share/applications\nICON_PATH = $(PREFIX)/share/icons\n\n"
  },
  {
    "path": "NEWS",
    "content": "----\nNEWS\n----\n\nNov 2000:\n\n  Introduced a new design layer between the IO and the Dw.\n\nMarch 2001:\n\n  Finally the new dillo widget is ready! (dillo >= 0.4.0).\n  0.4.0 is able to cope with low resolution depths.\n\nApr 2002:\n\n  We moved to:  http://dillo.cipsga.org.br/\n\nDec 2002\n\n  We moved to:  http://dillo.auriga.wearlab.de/\n\nJun 2003\n\n  http://www.dillo.org/     (hosted at the wearlab!)\n\nSep 2007\n\n  The new FLTK2-based dillo code is released! (under GPL3)\n\nJul 2011\n\n  Dillo switches to fltk-1.3.0.\n  dillo-2.2.1, the last of the 2.x series is released.\n\nSep 2011\n\n  dillo-3.0, the first of the 3.x series is released.\n\n  dillo-3.0.1, a bugfix release for the new 3.x series is out.\n\nMar 2013\n\n  dillo-3.0.3, a new release for the new 3.x series is out.\n\nApr 2014\n\n  dillo-3.0.4, a new release for the new 3.x series is out.\n\nDec 2014\n\n  dillo-3.0.4.1, a new release for the new 3.x series is out.\n\n\n  Jorge.-\n  jcid@dillo.org\n  Project maintainer, core developer, patcher, you name it! :-)\n\n"
  },
  {
    "path": "README",
    "content": "===================\n Dillo+ web browser\n===================\n\nDillo+ is a multi-platform graphical web browser, known for its\nspeed and small footprint, that is developed with a focus on\npersonal security and privacy.\n\nThe dillo3 series uses version 1.3.x of the FLTK GUI toolkit\n(http://fltk.org).\n\nThe core team currently plans to focus on implementing the CSS\nfeature of floating elements.  This will greatly improve\ndillo's web page rendering since many sites have adopted floats\ninstead of tables. \n\nThe core team welcomes developers willing to join our workforce. \n\n\nNOTE:  With  FLTK-1.3,  when  running  on X with Xft enabled (the\ndefault),  font naming is more restricted than it was with FLTK2\nas used by dillo2.  If your font_* preferences are no longer\nworking well, please try the fc-list command as it is shown in\ndillorc.\n\n\n  Here's a list of some old well-known problems of dillo:\n\n         * no FRAMES rendering\n\n\n--------\nFLTK-1.3\n--------\n\n  If you don't have FLTK-1.3 (try 'fltk-config --version' to check),\n  you can get it from:\n\n     http://fltk.org/software.php\n\n  and build it like:\n\n     tar zxvf fltk-1.3.3-source.tar.gz      [or latest 1.3.x version]\n     cd fltk-1.3.3\n     less README.Unix.txt\n     make\n     sudo make install\n     cd ..\n\n------\nDillo3\n------\n\n  Configure the options in Makefile.options.\n  Then:\n\n     make\n     sudo make install\n\n  In order to use the hyphenation feature, pattern files from CTAN need\n  to be installed.\n  This can be done with the script dillo-install-hyphenation.\n  Call it with ISO-639-1 language codes as arguments, or without arguments\n  to get more help.\n\n\n------------\nDpi programs\n------------\n\n  These  are  installed by \"make install\". If you don't have root\n  access,  copy  \"dillo\"  and \"dpid\" to some directory in your path\n  and  install  the  dpis by running \"./install-dpi-local\" from the\n  top directory (they will be installed under ~/.dillo).\n\n-----------\nDls scripts\n-----------\n\n  These too  are  installed by \"make install\".\n  You'll probably need python to run some of those.\n\n------------\nOS supported\n------------\n\nLinux, *BSD, Solaris are all supported, but be sure to personalize\nthe options in Makefile.options according to your OS.\n\n"
  },
  {
    "path": "README.md",
    "content": "# Overview\n[![screenshot](screenshots/main.png)](screenshots/main.png)\n\n**Dillo+** (Dillo-Plus) is a lightweight (about 10MB) *multi-protocol browser* for Linux, BSD and unix OS in general, that does not use Javascript but still support many advanced features.\n\nThis project is a port of the official Dillo that is located at https://dillo-browser.github.io/old/ with additions and fixes in part developed by me and in part merged from the DilloNG project located at https://github.com/w00fpack/dilloNG/.\n\nLightweight browsers, such as Dillo, are beneficial for loading websites quickly.  Also, lightweight web browsers can work on older hardware where common browser would take up too many resources, that could freeze up your computer.\n\nBecause javascript is not used, extra bloat is not needed to be downloaded.  If you have a slow internet connection, a web page that might have taken 3MB to download might only be 400kB with Dillo+.\n\nFinally, your online experience can be more secure with Dillo+. Not only does it not use Javascript, rules can also be defined per website domain. These rules can block connecting to certain domains, block ads and trackers, and require sites to use encryption.\n\nNote that not all websites will work correctly without Javascript, so be forewarned.\n\n# Browser Features\n\nMany features where added to the base Dillo code. Here are some highlight.\n\n## Gopher and Gemini protocols\n\nDillo+ supports the lightweight protocols **gopher** and **gemini** in addition to http and https. It aims to become your default browser for accessing the smol web.\n\nGopher screenshot:\n\n[![gopher](screenshots/gopher.png)](screenshots/gopher.png)\n\nGemini screenshot:\n\n[![gemini](screenshots/gemini.png)](screenshots/gemini.png)\n\n## Reader mode CSS style\n\nTo make it easier reading articles from the web, and also to rearrange large pages in a format easier on the eyes, it is possible to activate a reader mode CSS from the 'Tools' menu.\n\n## Additional content types\n\nSupport for rendering pages and local files written in the **gemini**, **gopher**, **markdown** and **rss** formats has been added.\n\n## Additional DPIs\n\nTo implement some advanced features, like **epubs** and **manpages** reading, a series of additional DPI modules has been added by default to the browser.\n\nCurrent additional DPIs are:\n- **File**: file browser able to render files according to their type, with some improvements over the original dillo DPI (URL protocol *file://*)\n- **Zip**: file browser for zip archives able to render compressed files according to their type, including HTML files (and their links and referenced resources) to easily visualize EPub ebooks (URL protocol *zip://*)\n- **Man**: man page viewer with man links support, very handy on a *nix system to quickly navigate the docs (URL protocol *man://*)\n- **Ftp**: file browser for the FTP protocol which also supports the OpenBSD ftp client and not only wget (URL protocol *ftp://*)\n- **Gemini**: browser for the Gemini protocol (URL protocol *gemini://*)\n- **Gopher**: browser for the Gopher protocol (URL protocol *gopher://*)\n\nHere are some screenshots of the Epub reader:\n\n[![epub1](screenshots/epub1.png)](screenshots/epub1.png)\n[![epub2](screenshots/epub2.png)](screenshots/epub2.png)\n\n## Searching\n\nIf wanted, there is a search dialog that is available by pressing 's'. It includes your search engines that are defined in your dillorc file. \n\n### Quick Searching\n\nYou can search common sites by typing one of the following key letters, followed by a space and your search term.\nFor example, 'dd pizza', will search the Duck Duck Go website for the term 'pizza'.\n\n```\ndd - to search DuckDuckGo\nse - to search SearX (searx.be)\nse2 - to search2 SearX (searxng.ch)\nse3 - to search3 SearX (searxng.ca)\nse4 - to search4 SearX (priv.au)\nya - to search Yandex\nwk - to search Wikipedia\nwt - to search Wiktionary\nwc - to search Wikicommons\nwb - to search Wikibooks\nwv - to search Wikiversity\nwy - to search Wikivoyage\nar - to search Internet Archive\nfd - to search Free Dictionary\nsp - to search Startpage\nmn - to search Marginalia\nte - to search Teclis\ngg - to search Google\nge - to search GeminiSpace\nlg - to search LibGen\ngr - to search GoodReads\npb - to search The Pirate Bay\ntw - to search Twitter\nun - to search Unsplash\ndv - to search Deviant Art\ndw - to search Debian Wiki\naw - to search Arch Wiki\ngw - to search Gentoo Wiki\now - to search OpenSuse Wiki\nsl - to search Slackware Docs\ndb - to search Debian Package\nap - to search Arch Package\nfp - to search Fedora Package\nob - to search OpenBSD Package\nos - to search OpenSuse Package\nfb - to search FreeBSD Man\n```\n\nTo modify these quick searches, refer to this link.\n\n## External media player\n\nDillo+ does not play media, such as audio and video, in the browser.  Instead you can run media using your preferred desktop media player.  The benefit is that playback will usually be more streamlined, without jitter.  Your media player might also have options, such as video caching of live streams, recording, streaming and downconverting.\n\n[![screenshot](screenshots/media_playing.png)](screenshots/media_playing.png))\n\n# Installation\n\n## Pre-built Releases\n\nPlease check under releases\n\n## Requirements\n\nPlease see https://dillo-browser.github.io/old/ for requirement details.\n\nIn brief, these dependencies are sufficient for the browser:\n* libiconv\n* FLTK 1.3\n* OpenSSL/LibreSSL\n\nTo enable image rendering install also:\n* libjpeg\n* libpng\n* libgif\n\nOn Debian-based systems with OpenSSL, you can run `apt install libiconv-hook1 libfltk1.3-dev libglx-dev libgl-dev libopengl-dev libssl-dev libxrender-dev libxfixes-dev libxcursor-dev libxext-dev libxinerama-dev libfontconfig1-dev libxft-dev libfontconfig-dev` to install dependencies, but some of these may need manually installed from the .deb (obtainable from `apt download [package]`) with `dpkg --force-all -i [path-to-package]`.\n\n## INSTALLATION AND COMPILING\n\nThe compilation system has been converted from the autotools suite used by the original dillo (see https://dillo-browser.github.io/old/ for the old install details) to a plain Makefile system.\n\nBefore compiling the code you can personalize some compilation options in the [Makefile.options](Makefile.options) files.\n\nSome preconfigured options are provided for common setups:\n* [Makefile.options.Linux](Makefile.options.Linux)\n* [Makefile.options.Linux.Wayland](Makefile.options.Linux.Wayland) (thanks to @XDream8)\n* [Makefile.options.OpenBSD](Makefile.options.OpenBSD)\n* [Makefile.options.MacOS](Makefile.options.MacOS) (thanks to @bouncepaw)\n\nReplace Makefile.options with the correct config for your system.\n\nAfter that just run:\n```\nmake\nmake install\n```\n\nSome additional instructions to compile Dillo+ on MacOs with Homebrew, and a prebuilt package can be found at this link: [dilloformacos](https://sites.google.com/view/dilloformacos/home-page) (thanks to Mepy).\n\n# Quickstart\n\n1. Obtain a release for your OS distribution, or compile Dillo from this repository's source code\n2. Install the application, and Click on the \"Dillo+\" menu option.\n3. Browse the internet the way you normally would\n\n# Contributing\n\nYou can access the original code repository at https://dillo-browser.github.io/old/. If you would like to use this GIT repository to submit code changes, please open pull requests against this GitHub repository. Here's some general guidelines when submitting PRs:\n\n * In your pull request, please:\n   * Describe the changes, why they were necessary, etc\n   * Describe how the changes affect existing behaviour\n   * Describe how you tested and validated your changes\n   * Include any relevant screenshots/evidence demonstrating that the changes work and have been tested\n\n**Enjoy using Dillo+!**\n"
  },
  {
    "path": "bm.txt",
    "content": ""
  },
  {
    "path": "config.h",
    "content": "/* config.h.  Generated from config.h.in by configure.  */\n/* config.h.in.  Generated from configure.ac by autoheader.  */\n\n/* Enable GIF images */\n#define ENABLE_GIF 1\n\n/* Enable JPEG images */\n#define ENABLE_JPEG 1\n\n/* Enable PNG images */\n#define ENABLE_PNG 1\n\n/* Enable SSL support */\n#define ENABLE_SSL 1\n\n/* Define to 1 if you have the <fcntl.h> header file. */\n#define HAVE_FCNTL_H 1\n\n/* Define to 1 if you have the `gethostbyname' function. */\n#define HAVE_GETHOSTBYNAME 1\n\n/* Define to 1 if you have the <inttypes.h> header file. */\n#define HAVE_INTTYPES_H 1\n\n/* Define to 1 if you have the `nsl' library (-lnsl). */\n/* #undef HAVE_LIBNSL */\n\n/* Define to 1 if you have the <libpng/png.h> header file. */\n/* #undef HAVE_LIBPNG_PNG_H */\n\n/* Define to 1 if you have the `socket' library (-lsocket). */\n/* #undef HAVE_LIBSOCKET */\n\n/* Define to 1 if you have the <memory.h> header file. */\n#define HAVE_MEMORY_H 1\n\n/* Define to 1 if you have the <png.h> header file. */\n/* #undef HAVE_PNG_H */\n\n/* Define to 1 if you have the `setsockopt' function. */\n#define HAVE_SETSOCKOPT 1\n\n/* Define to 1 if you have the <stdint.h> header file. */\n#define HAVE_STDINT_H 1\n\n/* Define to 1 if you have the <stdlib.h> header file. */\n#define HAVE_STDLIB_H 1\n\n/* Define to 1 if you have the <strings.h> header file. */\n#define HAVE_STRINGS_H 1\n\n/* Define to 1 if you have the <string.h> header file. */\n#define HAVE_STRING_H 1\n\n/* Define to 1 if you have the <sys/stat.h> header file. */\n#define HAVE_SYS_STAT_H 1\n\n/* Define to 1 if you have the <sys/types.h> header file. */\n#define HAVE_SYS_TYPES_H 1\n\n/* Define to 1 if you have the <sys/uio.h> header file. */\n#define HAVE_SYS_UIO_H 1\n\n/* Define to 1 if you have the <unistd.h> header file. */\n#define HAVE_UNISTD_H 1\n\n/* Name of package */\n#define PACKAGE \"dillo\"\n\n/* Define to the address where bug reports for this package should be sent. */\n#define PACKAGE_BUGREPORT \"\"\n\n/* Define to the full name of this package. */\n#define PACKAGE_NAME \"dillo\"\n\n/* Define to the version of this package. */\n#define PACKAGE_VERSION \"3.3.0\"\n\n/* Define to the full name and version of this package. */\n#define PACKAGE_STRING \"dillo 3.3.0\"\n\n/* Define to the one symbol short name of this package. */\n#define PACKAGE_TARNAME \"dillo\"\n\n/* Define to the home page for this package. */\n#define PACKAGE_URL \"\"\n\n/* The size of `char', as computed by sizeof. */\n#define SIZEOF_CHAR 1\n\n/* The size of `int', as computed by sizeof. */\n#define SIZEOF_INT 4\n\n/* The size of `long', as computed by sizeof. */\n#define SIZEOF_LONG 8\n\n/* The size of `short', as computed by sizeof. */\n#define SIZEOF_SHORT 2\n\n/* The size of `void *', as computed by sizeof. */\n#define SIZEOF_VOID_P 8\n\n/* Define to 1 if you have the ANSI C header files. */\n#define STDC_HEADERS 1\n\n/* Version number of package */\n#define VERSION \"3.3.0\"\n\n/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,\n   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the\n   #define below would cause a syntax error. */\n/* #undef _UINT32_T */\n\n/* Use char pointers for newer libiconv */\n#define inbuf_t char\n\n/* Define to the type of a signed integer type of width exactly 16 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef int16_t */\n\n/* Define to the type of a signed integer type of width exactly 32 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef int32_t */\n\n/* Define the real type of socklen_t */\n/* #undef socklen_t */\n\n/* Define to the type of an unsigned integer type of width exactly 16 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef uint16_t */\n\n/* Define to the type of an unsigned integer type of width exactly 32 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef uint32_t */\n"
  },
  {
    "path": "d_size.h",
    "content": "#ifndef __D_SIZE_H__\n#define __D_SIZE_H__\n\n\n#include \"config.h\"\n\n#ifdef HAVE_STDINT_H\n#include <stdint.h>\n#else\n#ifdef HAVE_INTTYPES_H\n#include <inttypes.h>\n#else\n/* config.h defines {int,uint}*_t */\n#endif /* HAVE_INTTYPES_H */\n#endif /*HAVE_STDINT_H */\n\ntypedef unsigned char   uchar_t;\ntypedef unsigned short  ushort_t;\ntypedef unsigned long   ulong_t;\ntypedef unsigned int    uint_t;\ntypedef unsigned char   bool_t;\n\n\n#endif /* __D_SIZE_H__ */\n"
  },
  {
    "path": "devdoc/CCCwork.txt",
    "content": "Last review: August 04, 2009 --jcid\n\n\n----------------------------\nInternal working for the CCC\n----------------------------\n\n\nHTTP protocol\n-------------\n\n\n      Query:                              |\n                                          .\n          1B --> 1B  1B --> 1B      -->   |  -------------.\n      .----.     .----.     .----.        .               |\nI     |Capi|     |http|     | IO |        |               |\n      '----'     '----'     '----'        .               |\n          1F <-- 1F  1F <-- 1F            |               V\n                                          .\n                                          |           [Server]\n      Answer:                             .\n\n          2B --> 2B  2B --> 2B            |               |\n      .----.     .----.     .----.        .               |\nII    |Capi|     |Dpi |     | IO |        |               |\n      '----'     '----'     '----'        .               |\n          2F <-- 2F  2F <-- 2F      <--   |  <------------'\n                                          .\n                                          |\n\n*  a_Capi_open_url()  builds  both the Answer and Query chains at\nonce  (Answer  first  then  Query), to ensure a uniform structure\nthat avoids complexity (e.g. race conditions).\n\n*  Http_get()  sets  a  callback  for  the  DNS hostname resolve.\nNormally  it  comes  later, but may also by issued immediately if\nthe hostname is cached.\n\n*  The  socket FD is passed by means of OpSend by the http module\nonce the remote IP is known and the socket is connected.\n\n\n\nFunction calls for HTTP CCC\n---------------------------\n\n  a_Capi_open_url\n    if (reload)\n      Capi OpStart 2B (answer)    [Capi] --> [dpi]  --> [IO]\n      Capi OpStart 1B (query)     [Capi] --> [http] --> [IO]\n        Http_get\n    a_Cache_open_url\n      if URL_E2EReload -> prepare reload\n      if cached\n        client enqueue\n        delayed process queue\n      else\n        Cache_entry_add\n        client enqueue\n\n    -//->\n    a_Http_dns_cb\n      Http_connect_socket\n        OpSend FD, BCK\n        OpSend FD, FWD\n        Http_send_query\n          a_Http_make_query_str\n            OpSend, BCK\n              IO_submit\n                a_IOwatch_add_fd (DIO_WRITE, ...)\n\n\n  Note about 'web' structures. They're created using a_Web_new().\nThe  web.c  module  keeps a list of valid webs, so anytime you're\nunsure  of  a  weak  reference  to  'web', it can be checked with\na_Web_valid(web).\n\n\n\n------------\nDpi protocol\n------------\n\n\n      Query:                              |\n                                          .\n          1B --> 1B  1B --> 1B      -->   |  -------------.\n      .----.     .----.     .----.        .               |\nI     |Capi|     |Dpi |     | IO |        |               |\n      '----'     '----'     '----'        .               |\n          1F <-- 1F  1F <-- 1F            |               V\n                                          .\n                                          |           [Server]\n                                          .\n      Answer (same as HTTP):              |               |\n                                          .               |\n          2B --> 2B  2B --> 2B            |               |\n      .----.     .----.     .----.        .               |\nII    |Capi|     |Dpi |     | IO |        |               |\n      '----'     '----'     '----'        .               |\n          2F <-- 2F  2F <-- 2F      <--   |  <------------'\n                                          .\n                                          |\n\n\nCCC Construction:\n\n  a_Capi_open_url()  calls  a_Capi_dpi_send_cmd()  when  the  URL\nbelongs to a dpi and it is not cached.\n\n  a_Capi_dpi_send_cmd()  builds  both the Answer and Query chains\nat  once (Answer first then Query), in the same way as HTTP does.\nNote  that  the  answer chain is the same for both, and the query\nchain only differs in the module in the middle ([http] or [dpi]).\n\n\nFunction calls for DPI CCC\n--------------------------\n\n  a_Capi_open_url\n    a_Capi_dpi_send_cmd\n      Capi OpStart 2B (answer)    [Capi] --> [dpi]  --> [IO]\n      Capi OpStart 1B (query)     [Capi] --> [http] --> [IO]\n      a_Cache_open_url\n        [...]\n\n\nNormal termination:\n\n  When  the dpi server is done, it closes the FD, and OpEnd flows\nfrom  IO  to  Capi (answer branch). When in Capi, capi propagates\nOpEnd to the query branch.\n\nAbnormal termination:\n\n  The  transfer may be aborted by a_Capi_conn_abort_by_url(). The\nOpAbort is not yet standardized and has an ad-hoc implementation.\nOne idea is to have OpAbort always propagate BCK and then FWD and\nto jump into the other chain when it gets to [Capi].\n\n\nDebugging CCC\n-------------\n\n  A  simple way to \"look\" inside it, is to \"#define VERBOSE 1\" in\nchain.c,  and  then to follow its work with a printed copy of the\ndiagrams in this document.\n\n  Each new data request generates a CCC, so if you want to debug,\nit's  good  to refine the testcase to the minimum possible number\nof connections.\n\n"
  },
  {
    "path": "devdoc/Cache.txt",
    "content": " June 2000, --Jcid\n Last update: Jul 09\n\n                              -------\n                               CACHE\n                              -------\n\n   The  cache  module  is  the  main  abstraction  layer  between\nrendering and networking.\n\n   The  capi module acts as a discriminating wrapper which either\ncalls  the  cache  or  the  dpi routines depending on the type of\nrequest.\n\n   Every  URL  must be requested using a_Capi_open_url, which\nsends the request to the cache if the data is cached, to dillo's\nhttp module for http: URLs, and through dillo's DPI system for\nother URLs.\n\n   Here we'll document non dpi requests.\n\n\n                         ----------------\n                         CACHE PHILOSOPHY\n                         ----------------\n\n   Dillo's  cache  is  very  simple; every single resource that's\nretrieved  (URL)  is  kept  in  memory. NOTHING is saved to disk.\nThis is mainly for three reasons:\n\n   - Dillo encourages personal privacy and it assures there'll be\nno recorded tracks of the sites you visited.\n\n   -  The Network is full of intermediate transparent proxys that\nserve as caches.\n\n   -  If  you still want to have cached stuff, you can install an\nexternal cache server (such as WWWOFFLE), and benefit from it.\n\n\n                         ---------------\n                         CACHE STRUCTURE\n                         ---------------\n\n   Currently, dillo's cache code is spread in different sources:\nmainly  in  cache.[ch],  dicache.[ch]  and  it  uses  some  other\nfunctions from mime.c and web.cc.\n\n   Cache.c  is  the  principal  source,  and  it also is the one\nresponsible  for  processing  cache-clients  (held  in  a queue).\nDicache.c  is  the interface to the decompressed RGB representations\nof currently-displayed images held in DW's imgbuf.\n\n   mime.c  and  web.cc  are  used  for secondary tasks such as\nassigning the right \"viewer\" or \"decoder\" for a given URL.\n\n\n----------------\nA bit of history\n----------------\n\n   Some  time  ago,  the  cache  functions,  URL  retrieval  and\nexternal  protocols  were  a whole mess of mixed code, and it was\ngetting  REALLY hard to fix, improve or extend the functionality.\nThe  main  idea  of  this  \"layering\" is to make code-portions as\nindependent  as  possible  so  they  can  be  understood,  fixed,\nimproved or replaced without affecting the rest of the browser.\n\n   An  interesting  part of the process is that, as resources are\nretrieved,  the  client  (dillo  in  this  case) doesn't know the\nContent-Type  of the resource at request-time. It only becomes known\nwhen  the  resource  header  is retrieved (think of http). This\nhappens  when  the  cache  has control, so the cache sets the\nproper  viewer for it (unless the Callback function was already\nspecified with the URL request).\n\n   You'll find a good example in http.c.\n\n   Note: All resources received by the cache have HTTP-style headers.\n   The file/data/ftp DPIs generate these headers when sending their\n   non-HTTP resources. Most importantly, a Content-Type header is\n   generated based on file extension or file contents.\n\n\n-------------\nCache clients\n-------------\n\n   Cache clients MUST use a_Capi_open_url to request an URL. The\nclient structure and the callback-function prototype are defined,\nin cache.h, as follows:\n\nstruct _CacheClient {\n   int Key;                 /* Primary Key for this client */\n   const DilloUrl *Url;     /* Pointer to a cache entry Url */\n   int Version;             /* Dicache version of this Url (0 if not used) */\n   void *Buf;               /* Pointer to cache-data */\n   uint_t BufSize;          /* Valid size of cache-data */\n   CA_Callback_t Callback;  /* Client function */\n   void *CbData;            /* Client function data */\n   void *Web;               /* Pointer to the Web structure of our client */\n};\n\ntypedef void (*CA_Callback_t)(int Op, CacheClient_t *Client);\n\n\n   Notes:\n\n   * Op is the operation that the callback is asked to perform\n   by the cache. { CA_Send | CA_Close | CA_Abort }.\n\n   * Client: The Client structure that originated the request.\n\n\n\n--------------------------\nKey-functions descriptions\n--------------------------\n\n\nint a_Cache_open_url(void *Web, CA_Callback_t Call, void *CbData)\n\n   if Web->url is not cached\n      Create a cache-entry for that URL\n      Send client to cache queue\n   else\n      Feed our client with cached data\n\n\n\n----------------------\nRedirections mechanism\n (HTTP 30x answers)\n----------------------\n\n  This is by no means complete. It's a work in progress.\n\n  Whenever  an  URL is served under an HTTP 30x header, its cache\nentry  is  flagged  with 'CA_Redirect'. If it's a 301 answer, the\nadditional  'CA_ForceRedirect'  flag  is  also set, if it's a 302\nanswer,  'CA_TempRedirect'  is  also set (this happens inside the\nCache_parse_header() function).\n\n  Later  on,  in Cache_process_queue(), when the entry is flagged\nwith 'CA_Redirect' Cache_redirect() is called.\n\n\n\n\n\n\n\n-----------\nNotes\n-----------\n\n   The  whole  process is asynchronous and very complex. I'll try\nto document it in more detail later (source is commented).\n   Currently  I  have  a drawing to understand it; hope the ASCII\ntranslation serves the same as the original.\n   If  you're  planning to understand the cache process thoroughly,\nwrite  me  a  note and I will assign higher priority to further\nimprovement of this doc.\n   Hope this helps!\n\n\n"
  },
  {
    "path": "devdoc/Dillo.txt",
    "content": "\"Eliminate the guesswork and quality goes up.\"\n\n\n                             -------\n                              DILLO\n                             -------\n\n   These  notes are written with a view to make it less hard, not\neasier yet ;), to get into Dillo development.\n   When I first got into it, I was totally unaware of the browser\ninternals.  Now  that  I've made my way deep into the core of it,\n(we  rewrote it 90% and modified the rest), is time to write some\ndocumentation,  just  to make a less steep learning curve for new\ndevelopers.\n\n   --Jcid\n\n\n\n                            --------\n                            OVERVIEW\n                            --------\n\n   Dillo can be viewed as the sum of five main parts:\n\n   1.- Dillo Widget: A custom widget, FLTK-based, that holds the\nnecessary data structures and mechanisms for graphical rendering.\n(Described in Dw*.txt, dw*.c files among the sources.)\n\n   2.-  Dillo Cache: Integrated with a signal driven Input/Output\nengine  that  handles file descriptor activity, the cache acts as\nthe  main  abstraction  layer  between  rendering and networking.\n   Every  URL,  whether  cached  or  not, must be retrieved using\na_Capi_open_url   (Described   briefly   in   Cache.txt,  source\ncontained in capi.c).\n   IO is described in IO.txt (recommended), source in src/IO/.\n\n   3.-  The  HTML  parser: A streamed parser that joins the Dillo\nWidget  and  the  Cache  functionality  to make browsing possible\n(Described in HtmlParser.txt, source mainly inside html.cc).\n\n   4.-  Image  processing  code:  The  part  that  handles  image\nretrieval,  decoding,  caching  and  displaying.  (Described  in\nImages.txt.   Sources:  image.c,  dw/image.cc,  dicache.c,  gif.c,\njpeg.c and png.c)\n\n   5.- The dpi framework: a gateway to interface the browser with\nexternal programs (Example: the bookmarks server plugin).\nDpi spec: https://dillo-browser.github.io/old/dpi1.html\n\n\n                      -------------------------\n                      HOW IS THE PAGE RENDERED?\n                      -------------------------\n\n(A short description of the internal function calling process)\n\n   When  the  user requests a new URL, a_UIcmd_open_url\nis  queried to do the job; it calls a_Nav_push (The highest level\nURL  dispatcher); a_Nav_push updates current browsing history and\ncalls  Nav_open_url.  Nav_open_url closes all open connections by\ncalling  a_Bw_stop_clients,  and then calls\na_Capi_open_url which calls a_Cache_open_url (or the dpi module if\nthis gateway is used).\n\n   If  Cache_entry_search  hits  (due to a cached url :), the client is\nfed  with cached data, but if the URL isn't cached yet, a new CCC\n(Concomitant  Control Chain) is created and committed to fetch the\nURL.\n\n   The  next  CCC  link  is dynamically assigned by examining the\nURL's protocol. It can be a_Http_ccc or a_Dpi_ccc.\n\n   If  we  have an HTTP URL, a_Http_ccc will succeed, and the http\nmodule  will  be linked; it will create the proper HTTP query and\nlink the IO module to submit and deliver the answer.\n\n   Note  that  as the Content-Type of the URL is not always known\nin  advance, the answering branch decides where to dispatch it to\nupon HTTP header arrival.\n\n\n   What happens then?\n\n   Well,  the  html  parser  gets  fed,  and proper functions are\ncalled  for  each tag (to parse and call the appropriate methods)\nand the whole page is contructed in a streamed way.\n   Somewhere  in  the  middle of it, resize and repaint functions\nare  activated  and  idle  functions draw to screen what has been\nprocessed.\n\n   (The process for images is described in Images.txt)\n\n\n\n\n"
  },
  {
    "path": "devdoc/Dpid.txt",
    "content": "Aug 2003, Jorge Arellano Cid,\n           Ferdi Franceschini --\nLast update: Nov 2009\n\n\n                               ------\n                                dpid\n                               ------\n\n-------------\nNomenclature:\n-------------\n\n  dpi:\n    generic term referring to dillo's plugin system (version1).\n\n  dpi1:\n    specific term for dillo's plugin spec version 1.\n    at: http://dillo-browser.github.io/old/dpi1.html\n\n  dpi program:\n    any plugin program itself.\n\n  dpi framework:\n    the code base inside and outside dillo that makes dpi1\n    working possible (it doesn't include dpi programs).\n\n  dpip:\n    dillo plugin protocol. The HTML/XML like set of command tags\n    and information that goes inside the communication sockets.\n    Note: not yet fully defined, but functional.\n    Note2: it was designed to be extensible.\n\n  dpid:\n    dillo plugin daemon.\n\n  server plugin:\n    A plugin that is capable of accepting connections on a socket.  Dpid will\n    never run more than one instance of a server plugin at a time.\n\n  filter plugin:\n    A dpi program that reads from stdin and writes to stdout, and that\n    exits after its task is done (they don't remain as server plugins).\n    Warning, dpid will run multiple instances of filter plugins if requested.\n\n-----------\nAbout dpid:\n-----------\n\n  * dpid is a program which manages dpi connections.\n  * dpid is a daemon that serves dillo using IDS sockets.\n  * dpid launches dpi programs and arranges socket communication\n    between the dpi program and dillo.\n\n The  concept  and  motivation  is  similar to that of inetd. The\nplugin  manager  (dpid) listens for a service request on a socket\nand  returns  the  socket/port  pair of a plugin that handles the\nservice.  It  also watches sockets of inactive plugins and starts\nthem when a connection is requested.\n\n\n-----------------------------------------------------------\nWhat's the problem with managing dpi programs inside dillo?\n-----------------------------------------------------------\n\n  That's the other way to handle it, but it started to show some\nproblems (briefly outlined here):\n\n  * When having two or more running instances of Dillo, one\n    should prevail, and take control of dpi managing (but all\n    dillos carry the managing code).\n  * If the managing-dillo exits, it must pass control to another\n    instance, or leave it void if there's no other dillo running!\n  * The need to synchronize all the running instances of\n    dillo arises.\n  * If the controlling instance finishes and quits, all the\n    dpi-program PIDs are lost.\n  * Terminating hanged dpis is hard if it's not done with signals\n    (PIDs)\n  * Forks can be expensive (Dillo had to fork its dpis).\n  * When a managing dillo exits, the new one is no longer the\n    parent of the forked dpis.\n  * If Unix domain sockets for the dpis were to be named\n    randomly, it gets very hard to recover their names if the\n    controlling instance of dillo exits and another must \"take\n    over\" the managing.\n  * It increments dillo's core size.\n  * If dillo hangs/crashes, dpi activity is lost (e.g. downloads)\n  * ...\n\n  That's why the managing daemon scheme was chosen.\n\n\n----------------------\nWhat does dpid handle?\n----------------------\n\n  It solves all the above mentioned shortcomings and also can do:\n\n  *  Multiple dillos:\n     dpid can communicate and serve more than one instance\n     of dillo.\n\n  *  Multiple dillo windows:\n     two or more windows of the same dillo instance accessing dpis\n     at the same time.\n\n  *  Different implementations of the same service\n     dpi  programs  (\"dpis\")  are  just  an  implementation  of a\n     service.  There's no problem in implementing a different one\n     for the same service (e.g. downloads).\n\n  *  Upgrading a service:\n     to   a  new  version  or  implementation  without  requiring\n     patching dillo's core or even bringing down the dpid.\n\n\n  And  finally,  being  aware  that  this  design can support the\nfollowing functions is very helpful:\n\n             SCHEME                      Example\n  ------------------------------------------------------------\n  * \"one demand/one response\"         man, preferences, ...\n  * \"resident while working\"          downloads, mp3, ...\n  * \"resident until exit request\"     bookmarks, ...\n\n  * \"one client only\"                 cd burner, ...\n  * \"one client per instance\"         man, ...\n  * \"multiple clients/one instance\"   downloads, cookies ...\n\n\n--------\nFeatures\n--------\n  * Dpi programs go in: \"EPREFIX/dillo/dpi\" or \"~/.dillo/dpi\". The binaries\n    are named <name>.dpi as \"bookmarks.dpi\" and <name>.filter.dpi as in\n    \"hello.filter.dpi\".  The  \".filter\"  plugins simply read from stdin\n    and write to stdout.\n  * Register/update/remove dpis from list of available dpis when a\n    'register_all' command is received.\n  * dpid terminates when it receives a 'DpiBye' command.\n  * dpis can be terminated with a 'DpiBye' command.\n  * dpidc control program for dpid, currently allows register and stop.\n\n\n-----\ntodo:\n-----\n\n These features are already designed, waiting for implementation:\n\n  * dpidc remove     // May be not necessary after all...\n\n\n-----------------\nHow does it work?\n-----------------\n\no    on startup dpid reads dpidrc for the path to the dpi directory\n     (usually EPREFIX/lib/dillo/dpi). ~/.dillo/dpi is scanned first.\n\no    both directories are scanned for the list of available plugins.\n     ~/.dillo/dpi overrides system-wide dpis.\n\no    next it creates internet domain sockets for the available plugins and\n     then listens for service requests on its own socket,\n     and for connections to the sockets of inactive plugins.\n\no    dpid returns the port of a plugin's socket when a client (dillo)\n     requests a service.\n\no    if the requested plugin is a 'server' then\n     1) dpid stops watching the socket for activity\n     2) forks and starts the plugin\n     3) resumes watching the socket when the plugin exits\n\no    if the requested plugin is a 'filter' then\n     1) dpid accepts the connection\n     2) maps the socket fd to stdin/stdout (with dup2)\n     3) forks and starts the plugin\n     4) continues to watch the socket for new connections\n\n\n\n\n---------------------------\ndpi service process diagram\n---------------------------\n\n  These drawings should be worth a thousand words! :)\n\n\n(I)\n     .--- s1 s2 s3 ... sn\n     |\n  [dpid]                     [dillo]\n     |\n     '--- srs\n\n  The dpid is running listening on several sockets.\n\n\n(II)\n     .--- s1 s2 s3 ... sn\n     |\n  [dpid]                     [dillo]\n     |                          |\n     '--- srs ------------------'\n\n  dillo needs a service so it connects to the service request\n  socket of the dpid (srs) and asks for the socket name of the\n  required plugin (using dpip).\n\n\n(III)\n     .--- s1 s2 s3 ... sn\n     |          |\n  [dpid]        |            [dillo]\n     |          |               |\n     '--- srs   '---------------'\n\n  then it connects to that socket (s3, still serviced by dpid!)\n\n\n(IV)\n     .--- s1 s2 s3 ... sn\n     |          |\n .[dpid]        |            [dillo]\n .   |          |               |\n .   '--- srs   '---------------'\n .\n .............[dpi program]\n\n  when s3 has activity (incoming data), dpid forks the dpi\n  program for it...\n\n\n(V)\n     .--- s1 s2 (s3) ... sn\n     |\n  [dpid]                     [dillo]\n     |                          |\n     '--- srs   .---------------'\n                |\n              [dpi program]\n\n  ... and lets it \"to take over\" the socket.\n\n  Once  there's  a  socket  channel  for dpi and dillo, the whole\ncommunication  process  takes  place until the task is done. When\nthe dpi program exits, dpid resumes listening on the socket (s3).\n\n\n--------------------------------\nSo, how do I make my own plugin?\n--------------------------------\n\n  Maybe  the  simplest  way to get started is to understand a few\nconcepts  and  then to use the hands-on method by using/modifying\nthe  hello  dpi.  It's  designed  as an example to get developers\nstarted.\n\n  ---------\n  Concepts:\n  ---------\n\n    * Dillo plugins work by communicating two processes: dillo\n      and the dpi.\n    * The underlying protocol (DPIP) has a uniform API which is\n      powerful  enough  for both blocking and nonblocking IO, and\n      filter or server dpis.\n    * The simplest example is one-request one-answer (for example\n      dillo  asks  for  a  URL and the dpi sends it). You'll find\n      this and more complex examples in hello.c\n\n  First, you should get familiar with the hello dpi as a user:\n\n    $dillo dpi:/hello/\n\n  Once  you've  played  enough  with  it,  start reading the well\ncommented code in hello.c and start making changes!\n\n\n  ---------------\n  Debugging a dpi\n  ---------------\n\n  The  simplest way is to add printf-like feedback using the MSG*\nmacros.  You  can  start  the dpid by hand on a terminal to force\nmessages  to  go  there.  Filter  dpis use sdterr and server dpis\nstdout. \n\n  Sometimes  more  complex dpis need more than MSG*. In this case\nyou can use gdb like this.\n\n    1.- Add an sleep(20) statement just after the dpi starts.\n    2.- Start  dillo and issue a request for your dpi. This will\n        get your dpi started.\n    3.- Standing in the dpi source directory:\n        ps aux|grep dpi\n    4.- Take note of the dpi's PID and start gdb, then:\n        (gdb) attach <PID>\n    5.- Continue from there...\n\n\n  ------------\n  Final Notes:\n  ------------\n\n  1.-  If  you  already  understand the hello dpi and want to try\nsomething more advanced:\n\n    * bookmarks.c is a good example of a blocking server\n    * file.c is an advanced example of a server handling multiple\n      non-blocking connections with select().\n\n  2.-   Multiple   instances  of  a  filter  plugin  may  be  run\nconcurrently, this could be a problem if your plugin records data\nin  a  file,  however  it  is safe if you simply write to stdout.\nAlternatively  you  could write a 'server' plugin instead as they\nare guaranteed not to run concurrently.\n\n  3.- The hardest part is to try to modify the dpi framework code\ninside  dillo; you have been warned! It already supports a lot of\nfunctionality,  but if you need to do some very custom stuff, try\nextending the \"chat\" command, or asking in dillo-dev.\n\n\n\n          >>>>>>>>>>>>>>>>>>>>>       <<<<<<<<<<<<<<<<<<<<<\n\n"
  },
  {
    "path": "devdoc/HtmlParser.txt",
    "content": " October 2001, --Jcid\n Last update: Jul 2009\n\n                        ---------------\n                        THE HTML PARSER\n                        ---------------\n\n\n   Dillo's  parser is more than just a HTML parser, it does XHTML\nand  plain  text  also.  It  has  parsing 'modes' that define its\nbehaviour while working:\n\n   typedef enum {\n     DILLO_HTML_PARSE_MODE_INIT = 0,\n     DILLO_HTML_PARSE_MODE_STASH,\n     DILLO_HTML_PARSE_MODE_STASH_AND_BODY,\n     DILLO_HTML_PARSE_MODE_BODY,\n     DILLO_HTML_PARSE_MODE_VERBATIM,\n     DILLO_HTML_PARSE_MODE_PRE\n   } DilloHtmlParseMode;\n\n\n   The  parser  works  upon a token-grained basis, i.e., the data\nstream is parsed into tokens and the parser is fed with them. The\nprocess  is  simple:  whenever  the  cache  has new data, it is\npassed to Html_write, which groups data into tokens and calls the\nappropriate functions for the token type (tag, space, or word).\n\n   Note:   when  in  DILLO_HTML_PARSE_MODE_VERBATIM,  the  parser\ndoesn't  try  to  split  the  data stream into tokens anymore; it\nsimply collects until the closing tag.\n\n------\nTOKENS\n------\n\n  * A chunk of WHITE SPACE     --> Html_process_space\n\n\n  * TAG                        --> Html_process_tag\n\n    The tag-start is defined by two adjacent characters:\n\n      first : '<'\n      second: ALPHA | '/' | '!' | '?'\n\n      Note: comments are discarded ( <!-- ... --> )\n\n\n    The tag's end is not as easy to find, nor to deal with!:\n\n    1)  The  HTML  4.01  sec.  3.2.2 states that \"Attribute/value\n    pairs appear before the final '>' of an element's start tag\",\n    but it doesn't define how to discriminate the \"final\" '>'.\n\n    2) '<' and '>' should be escaped as '&lt;' and '&gt;' inside\n    attribute values.\n\n    3)  The XML SPEC for XHTML states:\n      AttrValue ::== '\"' ([^<&\"] | Reference)* '\"'   |\n                     \"'\" ([^<&'] | Reference)* \"'\"\n\n    Current  parser  honors  the XML SPEC.\n\n    As  it's  a  common  mistake  for human authors to mistype or\n    forget  one  of  the  quote  marks of an attribute value; the\n    parser   solves  the  problem  with  a  look-ahead  technique\n    (otherwise  the  parser  could  skip significant amounts of\n    properly-written HTML).\n\n\n\n  * WORD                       --> Html_process_word\n\n    A  word is anything that doesn't start with SPACE, that's\n    outside  of  a  tag, up to the first SPACE or tag start.\n\n    SPACE = ' ' | \\n | \\r | \\t | \\f | \\v\n\n\n-----------------\nTHE PARSING STACK\n-----------------\n\n  The parsing state of the document is kept in a stack:\n\n  class DilloHtml {\n     [...]\n     lout::misc::SimpleVector<DilloHtmlState> *stack;\n     [...]\n  };\n\n  struct _DilloHtmlState {\n     CssPropertyList *table_cell_props;\n     DilloHtmlParseMode parse_mode;\n     DilloHtmlTableMode table_mode;\n     bool cell_text_align_set;\n     DilloHtmlListMode list_type;\n     int list_number;\n\n     /* TagInfo index for the tag that's being processed */\n     int tag_idx;\n\n     dw::core::Widget *textblock, *table;\n\n     /* This is used to align list items (especially in enumerated lists) */\n     dw::core::Widget *ref_list_item;\n\n     /* This is used for list items etc; if it is set to TRUE, breaks\n        have to be \"handed over\" (see Html_add_indented and\n        Html_eventually_pop_dw). */\n     bool hand_over_break;\n  };\n\n  Basically,  when a TAG is processed, a new state is pushed into\nthe  'stack'  and  its  'style'  is  set  to  reflect the desired\nappearance (details in DwStyle.txt).\n\n  That way, when a word is processed later (added to the Dw), all\nthe information is within the top state.\n\n  Closing TAGs just pop the stack.\n\n\n"
  },
  {
    "path": "devdoc/IO.txt",
    "content": "\nThis is the updated base of a paper I wrote with Horst.\nIt provides a good introduction to Dillo's internals.\n(Highly recommended if you plan to patch or develop in Dillo)\n\nIt may not be exactly accurate (it's quite old), but it explains\nthe theory behind in some detail, so it's more than recommended\nreading material.\n--Jcid\n\n\n-----------------------------------------------------\nParallel network programming of the Dillo web browser\n-----------------------------------------------------\n\n Jorge Arellano-Cid <jcid@dillo.org>\n Horst H. von Brand <vonbrand@inf.utfsm.cl>\n\n\n--------\nAbstract\n--------\n\n   Network  programs  face  several delay sources when sending or\nretrieving  data.  This  is  particularly problematic in programs\nwhich interact directly with the user, most notably web browsers.\nWe  present  a hybrid approach using threads communicated through\npipes  and  signal  driven  I/O, which allows a non-blocking main\nthread and overlapping waiting times.\n\n\n------------\nIntroduction\n------------\n\n   The Dillo project didn't start from scratch but mainly working\non  the  code base of gzilla (a light web browser written by Raph\nLevien). As the project went by, the code of the whole source was\nstandardized,  and the networking engine was replaced with a new,\nfaster  design.  Later,  the  parser  was  changed,  the streamed\nhandling  of data and its error control was put under the control\nof the CCC (Concomitant Control Chain), and the old widget system\nwas  replaced  with  a new one (Dw). The source code is currently\nregarded   as   \"very   stable   beta\",   and   is  available  at\n<http://www.dillo.org>. Dillo is a project licensed under the GNU\nGeneral Public License.\n\n   This  paper covers basic design aspects of the hybrid approach\nthat  the  Dillo  web  browser  uses  to  solve  several  latency\nproblems.  After  introducing  the  main  delay-sources, the main\npoints of the hybrid design will be addressed.\n\n\n-------------\nDelay sources\n-------------\n\n   Network  programs  face several delay-sources while sending or\nretrieving  data.  In  the particular case of a web browser, they\nare found in:\n\n  DNS querying:\n    The time required to solve a name.\n\n  Initiating the TCP connection:\n    The three way handshake of the TCP protocol.\n\n  Sending the query:\n    The time spent uploading queries to the remote server.\n\n  Retrieving data:\n    The time spent expecting and receiving the query answer.\n\n  Closing the TCP connection:\n    The four packet-sending closing sequence of the TCP protocol.\n\n   In  a  WAN  context,  every  single  item  of this list has an\nassociated  delay that is non deterministic and often measured in\nseconds. If we add several connections per browsed page (each one\nrequiring  at  least  the 4 last steps), the total latency can be\nconsiderable.\n\n\n-----------------------------------\nThe traditional (blocking) approach\n-----------------------------------\n\n   The main problems with the blocking approach are:\n\n     When issuing an operation that can't be completed\n     immediately, the process is put to sleep waiting for\n     completion, and the program doesn't do any other\n     processing in the meantime.\n\n     When waiting for a specific socket operation to complete,\n     packets that belong to other connections may be arriving,\n     and have to wait for service.\n\n     Web browsers handle many small transactions,\n     if waiting times are not overlapped\n     the latency perceived by the user can be very annoying.\n\n     If the user interface is just put to sleep during network\n     operations, the program becomes unresponsive, confusing\n     and perhaps alarming the user.\n\n     Not overlapping waiting times and processing makes\n     graphical rendering (which is arguably the central function\n     of a browser) unnecessarily slow.\n\n\n---------------------\nDillo's hybrid design\n---------------------\n\n   Dillo  uses  threads  and  signal  driven  I/O  extensively to\noverlap   waiting   times  and  computation.  Handling  the  user\ninterface  in a thread that never blocks gives a good interactive\n``feel.''  The  use of GTK+, a sophisticated widget framework for\ngraphical  user  interfaces,  helped very much to accomplish this\ngoal.  All the interface, rendering and I/O engine was built upon\nits facilities.\n\n   The  design  is  said to be ``hybrid'' because it uses threads\nfor  DNS  querying and reading local files, and signal driven I/O\nfor  TCP  connections.  The  threaded  DNS  scheme is potentially\nconcurrent  (this  depends on underlying hardware), while the I/O\nhandling   (both   local   files   and   remote  connections)  is\ndefinitively parallel.\n\n   To  simplify  the  structure  of  the browser, local files are\nencapsulated  into  HTTP streams and presented to the rest of the\nbrowser  as  such, in exactly the same way a remote connection is\nhandled.  To  create  this  illusion,  a thread is launched. This\nthread  opens  a  pipe  to  the  browser,  it then synthesizes an\nappropriate  HTTP  header, sends it together with the file to the\nbrowser  proper.  In  this way, all the browser sees is a handle,\nthe  data on it can come from a remote connection or from a local\nfile.\n\n   To  handle  a remote connection is more complex. In this case,\nthe  browser  asks the cache manager for the URL. The name in the\nURL  has  to  be  resolved  through  the DNS engine, a socket TCP\nconnection  must be established, the HTTP request has to be sent,\nand  finally  the  result  retrieved. Each of the steps mentioned\ncould  give  rise to errors, which have to be handled and somehow\ncommunicated to the rest of the program. For performance reasons,\nit  is  critical that responses are cached locally, so the remote\nconnection  doesn't  directly  hand over the data to the browser;\nthe  response is passed to the cache manager which then relays it\nto  the rest of the browser. The DNS engine caches DNS responses,\nand  either  answers  them from the cache or by querying the DNS.\nQuerying  is  done  in a separate thread, so that the rest of the\nbrowser isn't blocked by long waits here.\n\n   The  activities  mentioned do not happen strictly in the order\nstated  above.  It  is  even possible that several URLs are being\nhandled  at  the  same  time,  in  order  to  overlap waiting and\ndownloading.   The   functions  called  directly  from  the  user\ninterface   have   to  return  quickly  to  maintain  interactive\nresponse.  Sometimes they return connection handlers that haven't\nbeen completely set up yet. As stated, I/O is signal-driven, when\none  of  the  descriptors  is ready for data transfer (reading or\nwriting), it wakes up the I/O engine.\n\n   Data transfer between threads inside the browser is handled by\npipes,  shared  memory  is  little used. This almost obviates the\nneed for explicit synchronization, which is one of the main areas\nof  complexity and bugs in concurrent programs. Dillo handles its\nthreads  in  a way that its developers can think of it as running\non a single thread of control. This is accomplished by making the\nDNS  engine  call-backs  happen  within  the  main thread, and by\nisolating file loading with pipes.\n\n   Using threads in this way has three big advantages:\n\n     The browser doesn't block when one of its child threads\n     blocks. In particular, the user interface is responsive\n     even while resolving a name or downloading a file.\n\n     Developers don't need to deal with complex concurrent\n     concerns. Concurrency is hard to handle,  and few developers\n     are adept at this. This gives access a much larger pool of\n     potential developers, something which can be critical\n     in an open-source development project.\n\n     By making the code mostly sequential, debugging the code\n     with traditional tools like gdb is possible. Debugging\n     parallel programs is very hard, and appropriate tools are\n     hard to come by.\n\n   Because  of  simplicity and portability concerns, DNS querying\nis  done  in  a  separate  thread. The standard C library doesn't\nprovide  a  function for making DNS queries that don't block. The\nalternative  is  to implement a new, custom DNS querying function\nthat doesn't block. This is certainly a complex task, integrating\nthis  mechanism  into the thread structure of the program is much\nsimpler.\n\n   Using  a  thread  and  a  pipe  to  read  a  local file adds a\nbuffering step to the process (and a certain latency), but it has\na couple of significative advantages:\n\n     By handling local files in the same way as remote\n     connections, a significant amount of code is reused.\n\n     A preprocessing step of the file data can be added easily,\n     if needed. In fact, the file is encapsulated into an HTTP\n     data stream.\n\n\n-----------\nDNS queries\n-----------\n\n   Dillo handles DNS queries with threads, letting a child thread\nwait  until  the  DNS server answers the request. When the answer\narrives,  a call-back function is called, and the program resumes\nwhat  it  was doing at DNS-request time. The interesting thing is\nthat  the  call-back  happens in the main thread, while the child\nthread  simply  exits  when  done.  This is implemented through a\nserver-channel design.\n\n\nThe server channel\n------------------\n\n   There  is  one  thread  for each channel, and each channel can\nhave  multiple  clients. When the program requests an IP address,\nthe server first looks for a cached match; if it hits, the client\ncall-back  is  invoked immediately, but if not, the client is put\ninto  a  queue,  a thread is spawned to query the DNS, and a GTK+\nidle  client  is  set  to poll the channel 5~times per second for\ncompletion,  and  when  it finally succeeds, every client of that\nchannel is serviced.\n\n   This  scheme  allows all the further processing to continue on\nthe same thread it began: the main thread.\n\n\n------------------------\nHandling TCP connections\n------------------------\n\n   Establishing   a   TCP  connection  requires  the  well  known\nthree-way handshake packet-sending sequence. Depending on network\ntraffic  and several other issues, significant delay can occur at\nthis phase.\n\n   Dillo  handles the connection by a non blocking socket scheme.\nBasically,  a socket file descriptor of AF_INET type is requested\nand set to non-blocking I/O. When the DNS server has resolved the\nname, the socket connection process begins by calling connect(2);\n  {We use the Unix convention of identifying the manual section\n   where the concept is described, in this case\n   section 2 (system calls).}\nwhich  returns  immediately  with an EINPROGRESS error.\n\n   After  the  connection  reaches the EINPROGRESS ``state,'' the\nsocket  waits in background until connection succeeds (or fails),\nwhen  that  happens, a callback function is awaked to perform the\nfollowing  steps: set the I/O engine to send the query and expect\nits answer (both in background).\n\n   The  advantage  of  this scheme is that every required step is\nquickly  done  without  blocking the browser. Finally, the socket\nwill generate a signal whenever I/O is possible.\n\n\n----------------\nHandling queries\n----------------\n\n   In  the case of a HTTP URL, queries typically translate into a\nshort  transmission  (the  HTTP  query)  and  a lengthy retrieval\nprocess.  Queries  are  not  always  short though, specially when\nrequesting  forms  (all  the  form  data  is  attached within the\nquery), and also when requesting CGI programs.\n\n   Regardless  of  query  length,  query  sending  is  handled in\nbackground.  The thread that was initiated at TCP connecting time\nhas all the transmission framework already set up; at this point,\npacket   sending   is   just   a   matter   of  waiting  for  the\nwrite  signal  (G_IO_OUT) to come and then sending the data. When\nthe  socket  gets  ready for transmission, the data is sent using\nIO_write.\n\n\n--------------\nReceiving data\n--------------\n\n   Although  conceptually  similar to sending queries, retrieving\ndata is very different as the data received can easily exceed the\nsize  of  the query by many orders of magnitude (for example when\ndownloading  images or files). This is one of the main sources of\nlatency,  the  retrieval can take several seconds or even minutes\nwhen downloading large files.\n\n   The  data  retrieving process for a single file, that began by\nsetting up the expecting framework at TCP connecting time, simply\nwaits  for  the  read  signal  (G_IO_IN).  When  it  happens, the\nlow-level   I/O  engine  gets  called,  the  data  is  read  into\npre-allocated   buffers   and   the  appropriate  call-backs  are\nperformed.  Technically,  whenever  a G_IO_IN event is generated,\ndata  is  received  from the socket file descriptor, by using the\nIO_read function. This iterative process finishes upon EOF (or on\nan error condition).\n\n\n----------------------\nClosing the connection\n----------------------\n\n   Closing  a  TCP connection requires four data segments, not an\nimpressive  amount  but  twice  the round trip time, which can be\nsubstantial.  When  data  retrieval  finishes,  socket closing is\ntriggered. There's nothing but a IO_close_fd call on the socket's\nfile  descriptor.  This  process was originally designed to split\nthe  four  segment  close into two partial closes, one when query\nsending is done and the other when all data is in. This scheme is\nnot currently used because the write close also stops the reading\npart.\n\n\nThe low-level I/O engine\n------------------------\n\n   Dillo  I/O  is carried out in the background. This is achieved\nby  using  low level file descriptors and signals. Anytime a file\ndescriptor  shows  activity,  a  signal  is raised and the signal\nhandler takes care of the I/O.\n\n   The  low-level  I/O  engine  (\"I/O  engine\"  from here on) was\ndesigned  as  an  internal  abstraction layer for background file\ndescriptor  activity.  It  is  intended  to  be used by the cache\nmodule  only;  higher level routines should ask the cache for its\nURLs.  Every  operation  that  is  meant  to  be  carried  out in\nbackground  should  be  handled by the I/O engine. In the case of\nTCP sockets, they are created and submitted to the I/O engine for\nany further processing.\n\n   The  submitting process (client) must fill a request structure\nand let the I/O engine handle the file descriptor activity, until\nit  receives a call-back for finally processing the data. This is\nbetter understood by examining the request structure:\n\n typedef struct {\n    gint Key;              /* Primary Key (for klist) */\n    gint Op;               /* IORead | IOWrite | IOWrites */\n    gint FD;               /* Current File Descriptor */\n    gint Flags;            /* Flag array */\n    glong Status;          /* Number of bytes read, or -errno code */\n\n    void *Buf;             /* Buffer place */\n    size_t BufSize;        /* Buffer length */\n    void *BufStart;        /* PRIVATE: only used inside IO.c! */\n\n    void *ExtData;         /* External data reference (not used by IO.c) */\n    void *Info;            /* CCC Info structure for this IO */\n    GIOChannel *GioCh;     /* IO channel */\n    guint watch_id;        /* glib's event source id */\n } IOData_t;\n\n   To request an I/O operation, this structure must be filled and\npassed to the I/O engine.\n\n  'Op' and 'Buf' and 'BufSize' MUST be provided.\n\n  'ExtData' MAY be provided.\n\n  'Status',  'FD'  and  'GioCh'  are  set  by I/O engine internal\nroutines.\n\n   When  there  is new data in the file descriptor, 'IO_callback'\ngets  called  (by  glib).  Only  after  the  I/O  engine finishes\nprocessing the data are the upper layers notified.\n\n\nThe I/O engine transfer buffer\n------------------------------\n\n   The  'Buf' and  'BufSize'  fields  of  the  request  structure\nprovide  the transfer buffer for each operation. This buffer must\nbe set by the client (to increase performance by avoiding copying\ndata).\n\n   On  reads,  the client specifies the amount and where to place\nthe retrieved data; on writes, it specifies the amount and source\nof  the  data  segment  that  is to be sent. Although this scheme\nincreases  complexity,  it has proven very fast and powerful. For\ninstance,  when  the  size  of  a document is known in advance, a\nbuffer for all the data can be allocated at once, eliminating the\nneed for multiple memory reallocations. Even more, if the size is\nknown  and the data transfer is taking the form of multiple small\nchunks  of  data,  the  client  only  needs  to  update 'Buf' and\nBufSize'  to  point  to  the  next byte in its large preallocated\nreception  buffer  (by  adding  the  chunk size to 'Buf'). On the\nother  hand,  if the size of the transfer isn't known in advance,\nthe  reception  buffer  can remain untouched until the connection\ncloses,  but  the  client  must  then accomplish the usual buffer\ncopying and reallocation.\n\n   The  I/O  engine  also  lets  the client specify a full length\ntransfer  buffer  when  sending data. It doesn't matter (from the\nclient's  point  of  view) if the data fits in a single packet or\nnot,  it's  the I/O engine's job to divide it into smaller chunks\nif needed and to perform the operation accordingly.\n\n\n------------------------------------------\nHandling multiple simultaneous connections\n------------------------------------------\n\n   The  previous sections describe the internal work for a single\nconnection,  the  I/O engine handles several of them in parallel.\nThis  is the normal downloading behavior of a web page. Normally,\nafter   retrieving   the   main  document  (HTML  code),  several\nreferences  to  other files (typically images) and sometimes even\nto  other  sites  (mostly advertising today) are found inside the\npage.  In  order  to parse and complete the page rendering, those\nother  documents  must  be  fetched  and  displayed, so it is not\nuncommon  to  have  multiple  downloading  connections (every one\nrequiring the whole fetching process) happening at the same time.\n\n   Even  though  socket  activity  can  reach  a hectic pace, the\nbrowser  never  blocks.  Note also that the I/O engine is the one\nthat  directs  the  execution flow of the program by triggering a\ncall-back  chain whenever a file descriptor operation succeeds or\nfails.\n\n   A  key point for this multiple call-back chained I/O engine is\nthat  every  single  function  in the chain must be guaranteed to\nreturn  quickly.  Otherwise,  the  whole  system  blocks until it\nreturns.\n\n\n-----------\nConclusions\n-----------\n\n   Dillo is currently in very stable beta state. It already shows\nimpressive  performance,  and  its  interactive  ``feel'' is much\nbetter than that of other web browsers.\n\n   The modular structure of Dillo, and its reliance on GTK1 allow\nit  to  be  very  small.  Not every feature of HTML-4.01 has been\nimplemented  yet,  but  no  significant  problems are foreseen in\ndoing this.\n\n   The  fact  that  Dillo's  central  I/O engine is written using\nadvanced  features  of  POSIX  and  TCP/IP  networking  makes its\nperformance  possible, but on the other hand this also means that\nonly a fraction of the interested hackers are able to work on it.\n\n   A  simple code base is critical when trying to attract hackers\nto  work  on  a  project  like this one. Using the GTK+ framework\nhelped  both  in  creating  the  graphical  user interface and in\nhandling  the  concurrency  inside the browser. By having threads\ncommunicate  through  pipes the need for explicit synchronization\nis  almost  completely  eliminated,  and  with  it  most  of  the\ncomplexity of concurrent programming disappears.\n\n   A  clean,  strictly  applied  layering approach based on clear\nabstractions  is  vital  in  each  programming  project.  A good,\nsupportive framework is of much help here.\n\n\n"
  },
  {
    "path": "devdoc/Images.txt",
    "content": " January 2009, --Jcid\n\nUpdate June 2015: See also doc/dw-images-and-backgrounds.doc, or\n../html/dw-images-and-backgrounds.html (generated by doxygen).\n\n                              ------\n                              IMAGES\n                              ------\n\n* When a image tag is found within a HTML page, Html_tag_open_img\nhandles it by:\n\n   - Parsing & getting attribute values.\n   - Creating a new image structure (DilloImage) and its\n     associated widget (DwImage).\n     i.e. If 'Image' is the var for the structure, then\n          'Image->dw' is the widget.\n   - Requesting the image to be feeded by the cache.\n   - Sending some info to the browser interface.\n\n* The  cache  can  either request the image data from the net, or\nfeed it directly from the dicache (decompressed image cache).\n\n*  Both  processes  are  somewhat different because the first one\nrequires to decode the image data into RGB format, and the second\none has the whole data already decoded.\n\n*  Regardless of the RGB-data feeding method, the decoded data is\npassed to the widget (DwImage) and drawn in a streamed way.\n   Note that INDEXED images are also decoded into RGB format.\n\n   Html_tag_open_img    // IMG element processing\n     Html_add_new_image // Read attributes, create image, add to HTML page\n       a_Image_new      // Create a 'DilloImage' data structure, to coordinate\n                        // decoded image-data transfer to an 'Imgbuf'.\n       Html_add_widget  // Adds the dw::Image to the page\n       Html_load_image  // Tells cache to retrieve image\n\n\n---------------------\nFetching from the net\n---------------------\n\n*  a_Capi_open_url  initiates  the  resource  request,  and  when\nfinally  the answer arrives, the HTTP header is examined for MIME\ntype  and  either the GIF or PNG or JPEG decoder is set to handle\nthe incoming data stream.\n\n   Decoding functions:\n     a_Gif_callback, a_Jpeg_callback and a_Png_callback.\n\n*  The  decoding  function calls the following dicache methods as\nthe data is processed (listed in order):\n\n   a_Dicache_set_parms\n   a_Dicache_set_cmap (only for indexed-GIF images)\n   a_Dicache_write\n   a_Dicache_new_scan (MAY be called here or after set_cmap)\n   a_Dicache_close\n\n\n*  The  dicache  methods  call the necessary functions to connect\nwith the widget code. This is done by calling image.c functions:\n\n   a_Image_set_parms\n   a_Image_set_cmap\n   a_Image_write\n   a_Image_new_scan\n   a_Image_close\n\n* The functions in image.c make the required Dw calls.\n\n\n-------------------------\nFetching from the dicache\n-------------------------\n\n* a_Capi_open_url() tests the cache for the image, and the cache,\nvia  a_Cache_open_url(), enqueues a client for it, without asking\nthe  network  for the data. When the client queue is processed (a\nbit later), Dicache_image() is set as the callback.\n\n*  When  Dicache_image() is called, it sets the proper image data\ndecoder  (RGB)  and its data structure based on the entry's Type.\nThen  it  substitutes  itself  with  a_Dicache_callback()  as the\nhandling function, and gets out of the way.\n\n*  Thenceforth  the  rest  of  the  functions  calls is driven by\na_Dicache_callback().\n\n\n-----------\nMisc. notes\n-----------\n\n*  Repeated images generate new cache clients, but they may share\nthe  imgbuf.\n   Note:  Currently  there's  no  proper  support for transparent\nimages  (i.e.  decode  to RGBA), but most of the time they render\nthe  background  color  OK.  This is: when first loaded, repeated\nimages  share  a  background  color,  but when cached they render\ncorrectly ;-). There's no point in trying to fix this because the\ncorrect  solution is to decode to RGBA and let the toolkit (FLTK)\nhandle the transparency.\n\n*  The  first cache-client callback (Dicache_image()) is set when\nthe Content-type of the image is got.\n\n*  Later  on,  when  there's a shared imgbuf, the dicache's logic\navoids decoding it multiple times and reuses what's already done.\n\n*  The dicache-entry and the Image structure hold bit arrays that\nrepresent which rows have been decoded.\n\n* The image processing can be found in the following sources:\n\n  - image.{cc,hh}\n  - dicache.[ch]\n  - gif.[ch], png.[ch], jpeg.[ch]\n  - dw/image.{cc,hh}\n\n*  Bear  in  mind  that  there are four data structures for image\ncode:\n\n  - DilloImage (image.hh)\n  - DICacheEntry (dicache.h)\n  - dw::Image (class Image in dw/image.hh)\n  - core::Imgbuf (imgbuf.hh)\n\n"
  },
  {
    "path": "devdoc/NC_design.txt",
    "content": "\n     _________________________________________________________________\n\n                             Naming&Coding design\n     _________________________________________________________________\n\n   Dillo's code is divided into modules. For instance: bookmark, cache,\n   dicache, gif.\n\n   Let's think of a module named \"menu\", then:\n     * Every internal routine of the module, should start with \"Menu_\"\n       prefix.\n     * \"Menu_\" prefixed functions are not meant to be called from outside\n       the module.\n     * If the function is to be exported to other modules (i.e. it will\n       be called from the outside), it should be wrapped with an \"a_\"\n       prefix.\n\n   For instance: if the function name is \"Menu_create\", then it's an\n   internal function, but if we need to call it from the outside, then it\n   should be renamed to \"a_Menu_create\".\n\n   Why the \"a_\" prefix?\n   Because of historical reasons.\n   And \"a_Menu_create\" reads better than \"d_Menu_create\" because the\n   first one suggests \"a Menu create\" function!\n\n   Another way of understanding this is thinking of \"a_\" prefixed\n   functions as Dillo's internal library, and the rest (\"Menu_\" prefixed\n   in our example) as a private module-library.\n\n   Indentation:\n\n   Source code must be indented with 3 blank spaces, no Tabs.\n   Why?\n   Because different editors expand or treat tabs in several ways; 8\n   spaces being the most common, but that makes code really wide and\n   we'll try to keep it within the 80 columns bounds (printer friendly).\n\n   You can use:   indent -kr -sc -i3 -bad -nbbo -nut -l79 myfile.c\n\n   Function commenting:\n\n   Every single function of the module should start with a short comment\n   that explains its purpose; three lines must be enough, but if you\n   think it requires more, enlarge it.\n\n   /*\n    * Try finding the url in the cache. If it hits, send the contents\n    * to the caller. If it misses, set up a new connection.\n    */\n   int a_Cache_open_url(const char *url, void *Data)\n   {\n      ...\n      ...\n      ...\n   }\n\n   We also have the BUG:, TODO:, and WORKAROUND: tags.\n   Use them within source code comments to spot hidden issues. For\n   instance:\n\n   /* BUG: this counter is not accurate */\n   ++i;\n\n   /* TODO: get color from the right place */\n   a = color;\n\n   /* WORKAROUND: the canonical way of doing it doesn't work yet. */\n   ++a; ++a; ++a;\n\n   Function length:\n\n   Let's try to keep functions within the 45 lines boundary. This eases\n   code reading, following, understanding and maintenance.\n\n   Functions with a single exit:\n\n   It's much easier to follow and maintain functions with a single exit\n   point at the bottom (instead of multiple returns). The exception to\n   the rule are calls like dReturn_if_fail() at its head.\n\n   dlib functions:\n\n     * Dillo uses dlib extensively in its C sources. Before starting\n       to code something new, a good starting point is to check what\n       this library has to offer (check dlib/dlib.h).\n     * Memory management must be done using dNew, dNew0, dMalloc, dFree\n       and their relatives.\n     * For debugging purposes and error catching (not for normal flow):\n       dReturn_if_fail, dReturn_val_if_fail etc. are encouraged.\n     * The MSG macro is extensively used to output additional information\n       to the calling terminal.\n\n     _________________________________________________________________\n\n  C++\n\n   Source code in C++ should follow the same rules with these exceptions:\n\n     * Class method names are camel-cased and start with lowercase\n       e.g. appendInputMultipart\n     * Classes and types start uppercased\n       e.g. class DilloHtmlReceiver\n     * Class methods don't need to prefix its module name\n       e.g. links->get()\n\n   We also try to keep the C++ relatively simple. Dillo does use\n   inheritance and templates, but that's about all.\n\n     _________________________________________________________________\n\n  What do we get with this?\n\n     * A clear module API for Dillo; every function prefixed \"a_\" is to\n       be used outside the module.\n     * A way to identify where the function came from (the\n       capitalized word is the module name).\n     * An inner ADT (Abstract data type) for the module that can be\n       isolated, tested and replaced independently.\n     * A two stage instance for bug-fixing. You can change the exported\n       function algorithms while someone else fixes the internal\n       module-ADT!\n     * A coding standard ;)\n     _________________________________________________________________\n\n        Naming&Coding design by Jorge Arellano Cid\n"
  },
  {
    "path": "devdoc/README",
    "content": "README: Last update Jul 2009\n\nThese documents cover dillo's internals.\nFor user help, see http://dillo-browser.github.io/old/dillo3-help.html\n\n--------------------------------------------------------------------------\n\nThese documents need a review.\n*.txt were current with Dillo1, but many have since become more or\n      less out-of-date.\n*.doc are doxygen source for the Dillo Widget (dw) component, and\n      were written for Dillo2.\n\nThey will give you an overview of what's going on, but take them\nwith a pinch of salt.\n\n Of course I'd like to have *.txt as doxygen files too!\nIf somebody wants to make this conversion, please let me know\nto assign higher priority to updating these docs.\n\n--\nJorge.-\n\n --------------------------------------------------------------------------\n       FILE                    DESCRIPTION                   STATE\n --------------------------------------------------------------------------\n   NC_design.txt       Naming&Coding design (Be sure to     Current\n                       read it before any other doc)\n   Dillo.txt           General overview of the program      Current\n   IO.txt              Extensive introduction               Current\n   Cache.txt           Informative description              Current\n   Images.txt          Image handling and processing        Current\n   HtmlParser.txt      A versatile parser                   Current\n   Dw.txt              The New Dillo Widget (Overview)      Current\n   Imgbuf.txt          Image buffers                        Pending\n   Selection.txt       Selections, and link activation      Current (?)\n   Cookies.txt         Explains how to enable cookies       Current\n   Dpid.txt            Dillo plugin daemon                  Current\n --------------------------------------------------------------------------\n\n\n * BTW, there's a small program (srch) within the src/ dir. It searches\n tokens within the whole code (*.[ch]). It has proven very useful.\n Ex:  ./srch a_Image_write\n      ./srch todo:\n\n * Please submit your patches with 'hg diff'.\n\n\n Happy coding!\n --Jcid\n"
  },
  {
    "path": "devdoc/dw-changes.doc",
    "content": "/** \\page dw-changes Changes to the GTK+-based Release Version\n\n<h2>Changes in Dw</h2>\n\nRelated to the FLTK port, there have been many changes, this is a\n(hopefully complete) list:\n\n<ul>\n<li> Rendering abstraction, read \\ref dw-overview and  \\ref dw-layout-views\n     for details. Some important changes:\n\n     <ul>\n     <li> The underlying platform (e.g. the UI toolkit) is fully abstract,\n          there are several platform independent structures replacing\n          GTK+ structures, e.g. dw::core::Event.\n\n     <li> The central class managing the widget tree is not anymore\n          GtkDwViewport,  but dw::core::Layout.\n\n     <li> Drawing is done via dw::core::View, a pointer is passed to\n          dw::core::Widget::draw.\n\n     <li> The distinction between viewport coordinates and canvas\n          coordinates (formerly world coordinates) has been mostly\n          removed. (Only for views, it sometimes plays a role, see\n          \\ref dw-layout-views).\n</ul>\n\n<li> Cursors have been moved to dw::core::style, see\n     dw::core::style::Style::cursor. dw::core::Widget::setCursor is now\n     protected (and so only called by widget implementations).\n\n<li> World coordinates are now called canvas coordinates.\n\n<li> There is now a distinction between dw::core::style::StyleAttrs and\n     dw::core::style::Style.\n\n<li> There is no base class for container widgets anymore. The former\n     DwContainer::for_all has been removed, instead this functionality\n     is now done via iterators (dw::core::Widget::iterator,\n     dw::core::Iterator).\n\n<li> DwPage is now called dw::Textblock, and DwAlignedPage\n     dw::AlignedTextblock.\n\n<li> dw::Textblock, all sub classes of it, and dw::Table do not read\n     \"limit_text_width\" from the preferences, but get it as an argument.\n     (May change again.)\n\n<li> dw::Table has been rewritten.\n\n<li> Instead of border_spacing in the old DwStyle, there are two attributes,\n     dw::core::style::Style::hBorderSpacing and\n     dw::core::style::Style::vBorderSpacing, since CSS allowes to specify\n     two values. Without CSS, both attributes should have the same value.\n\n<li> Images are handled differently, see \\ref dw-images-and-backgrounds.\n\n<li> Embedded UI widgets (formerly GtkWidget's) are handled differently,\n     see dw::core::ui.\n\n<li> DwButton has been removed, instead, embedded UI widgets are used. See\n     dw::core::ui and dw::core::ui::ComplexButtonResource.\n</ul>\n\nDw is now written C++, the transition should be obvious. All \"Dw\"\nprefixes have been removed, instead, namespaces are used now:\n\n<ul>\n<li>dw::core contains the core,\n<li>dw::core::style styles,\n<li>dw::core::ui embedded UI resources,\n<li>dw::fltk classes related to FLTK, and\n<li>::dw the widgets.\n</ul>\n\n<h2>Documentation</h2>\n\nThe old documentation has been moved to:\n\n<table>\n<tr><th colspan=\"2\">Old           <th>New\n<tr><td rowspan=\"2\">Dw.txt\n    <td>general part              <td>\\ref dw-overview, \\ref dw-usage,\n                                      \\ref dw-layout-widgets,\n                                      \\ref dw-widget-sizes\n<tr><td>remarks on specific widgets  <td>respective source files: dw::Bullet,\n                                      dw::core::ui::Embed\n<tr><td rowspan=\"2\">DwImage.txt\n    <td>signals                   <td>dw::core::Layout::LinkReceiver\n<tr><td>rest                      <td>dw::Image,\n                                      \\ref dw-images-and-backgrounds\n<tr><td colspan=\"2\">Imgbuf.txt    <td>dw::core::Imgbuf,\n                                  \\ref dw-images-and-backgrounds\n<tr><td colspan=\"2\">DwPage.txt    <td>dw::Textblock\n<tr><td colspan=\"2\">DwRender.txt  <td>\\ref dw-overview, \\ref dw-layout-views,\n                                      dw::core::ui\n<tr><td colspan=\"2\">DwStyle.txt   <td>dw::core::style\n<tr><td colspan=\"2\">DwTable.txt   <td>dw::Table\n<tr><td colspan=\"2\">DwWidget.txt  <td>dw::core::Widget, \\ref dw-layout-widgets,\n                                      \\ref dw-widget-sizes\n<tr><td colspan=\"2\">Selection.txt <td>dw::core::SelectionState\n</table>\n\n*/\n"
  },
  {
    "path": "devdoc/dw-fixed-positions.doc",
    "content": "/** \\page dw-fixed-positions Fixed positions\n\nIn some cases, widgets or widget content must be positioned relative\nto the viewport. As in the CSS specification, these positions will be\ncalled \"fixed positions\". This must not be confused with \"fixedly\npositioned elements\" (see \\ref dw-out-of-flow), which are a special\ncase of fixed positions.\n\n\nApplications\n============\n\nAs defined by CSS\n-----------------\n\n- \"position: fixed\"; see  \\ref dw-out-of-flow.\n- \"background-attachment: fixed\"; see  \\ref dw-images-and-backgrounds.\n\nIdea for tables\n---------------\n\nOften, tables have a header, which contains informations necessary to\ninterpret the columns in the table body. For this, HTML defines the elements\n&lt;thead&gt; and &lt;tbody&gt;\n<sup><a href=\"#note-table-footer\" id=\"ref-table-footer\">[1]</a></sup>.\n\nFor large tables, the problem occurs that the table header gets out of\nthe reader's view. In paged media, where a large table covers multiple\npages, this is often solved by *repeating* the table header on each\npage occupied by the table. When using a viewport, a table larger than\nthe vieport could be displayed like this:\n\n1. If the top of the table is within the viewport, show the table\n   header at the usual position.\n2. As soon as top of the table gets above the top border of the\n   viewport, keep the table header at the viewport top, so that it is\n   still visible (this means, it moves down, relative to the\n   *canvas*). This way, the header is still visible, so our objective\n   is achieved.\n3. When scrolling further down, at some point the table body gets out\n   of the viewport again, and so should the table header.\n\n(Some images would be nice.)\n\nThese ideas should be considered when developing a design for fixed\npositions.\n\n\nDesign sketch\n==============\n\n[...]\n\n\n----------------------------------------------------------------------\n\n<sup><a href=\"#ref-table-footer\" id=\"note-table-footer\">[1]</a></sup>\n... and also &lt;tfoot&gt;, which is not discussed here, for reasons\nof simplicity. However, it is obvious that &lt;tfoot&gt; should be\ndealt with in an analogue way as &lt;thead&gt;.\n\n\n*/"
  },
  {
    "path": "devdoc/dw-grows.doc",
    "content": "/** \\page dw-grows GROWS - Grand Redesign Of Widget Sizes\n\n<div style=\"border: 2px solid #ffff00; margin: 1em 0;\n  padding: 0.5em 1em; background-color: #ffffe0\">The complex \"widget\n  sizes\" is currently divided into three documents: \\ref\n  dw-widget-sizes, **Grand Redesign Of Widget Sizes** (this document),\n  and \\ref dw-size-request-pos. Furthermore, there are some notes in\n  \\ref dw-miscellaneous.</div>\n\nThis paper describes (will describe) some design changes to\ncalculating widget sizes. Goals are:\n\n- Simplification of widget size calculation by the parent widget;\n  dw::Textblock::calcWidgetSize, dw::OutOfFlowMgr::ensureFloatSize\n  etc. should become simpler or perhaps even obsolete.\n\n- Making the implementation of some features possible:\n\n  - *max-width*, *max-height*, *min-width*, *min-height*;\n  - correct aspect ratio for images with only one percentage size defined;\n  - *display: inline-block*;\n  - &lt;button&gt;.\n\n\nA short sketch\n==============\n\n**dw::core::Widget::sizeRequest and dw::core::Widget::getExtremes will\nreturn final results.** The caller does not have to correct the size,\ne.&nbsp;g. when percentages are defined. As an example,\ndw::Textblock::calcWidgetSize has already become much simpler.\n\n**A new hierarchy, *container*:** Aside from dw::core::Widget::parent\nand dw::core::Widget::generator, there is a third hierarchy\ndw::core::Widget::container, which is (unlike *generator*) always a\ndirect ancestor, and represents what in CSS is called *containing\nblock*. Containers are important to define the \"context size\", which\nis (not solely) used for percentage sizes.\n\n(There is another \"containing block\", dw::Textblock::containingBlock;\nthese may be consolidated some day.)\n\n**The process of size calculation is split between the widget itself\nand its container:**\n\n- The container provides some abstract methods:\n  dw::core::Widget::getAvailWidthOfChild,\n  dw::core::Widget::getAvailHeightOfChild,\n  dw::core::Widget::correctRequisitionOfChild, and\n  dw::core::Widget::correctExtremesOfChild, which can be used in the\n  actual implementation of dw::core::Widget::sizeRequestImpl;\n  different containers with different ways how to arrange their\n  children will implement these methods in a different way. (Simple\n  example: the *available width* for children within a textblock is\n  the *available width* for the textblock itself, minus\n  margin/border/padding; on the other hand, it is completely different\n  for children of tables, for which a complex column width calculation\n  is used.)\n\n- The actual size calculation is, however, controlled by the widget\n  itself, which only *uses* these methods above.\n\n<div style=\"border: 2px solid #ffff00; margin-top: 0.5em;\n   margin-bottom: 0.5em; padding: 0.5em 1em; background-color: #ffffe0\">\n   <b>Update:</b> This is not fully correct; the parents are also involved\n   for calculating available widths and heights, at least when CSS 'width'\n   and 'height' are not set.</div>\n\n**Size hints are removed.** Instead, the container methods in the\nprevious paragraph are used. Changes of container sizes (especially\nviewport the size) are handled in a different way.\n\n**Extremes are extended by intrinsic values.** In some cases (see\ndw::Table::forceCalcCellSizes, case *minWidth* > *totalWidth*, for an\nexample) it is useful to know about minimal and maximal width of a\nwidget independent of CSS attributes. For this, dw::core::Extremes is\nextended by:\n\n- dw::core::Extremes::minWidthIntrinsic and\n- dw::core::Extremes::maxWidthIntrinsic.\n\nThe rules for the calculation:\n\n1. If a widget has no children, it calculates *minWidthIntrinsic* and\n   *maxWidthIntrinsic* as those values not affected by CSS hints.\n   (dw::core::Widget::correctExtremes will not change these values.)\n2. A widget must calculate *minWidthIntrinsic* and *maxWidthIntrinsic*\n   from *minWidthIntrinsic* and *maxWidthIntrinsic* of its children,\n   and *minWidth* and *maxWidth* from *minWidth* and *maxWidth* of its\n   children.\n3. At the end, *minWidth* and *maxWidth* of a widget are corrected by\n   CSS attributes. (dw::core::Widget::correctExtremes will do this.)\n\n<div style=\"border: 2px solid #ffff00; margin-top: 0.5em;\n   margin-bottom: 0.5em; padding: 0.5em 1em; background-color: #ffffe0\">\n   <b>Notice:</b> Currently, dw::core::Widget::getExtremesImpl must\n   set all four members in dw::core::Extremes; this may change.</div>\n\nAnother **extension of extremes: *adjustmentWidth*.** This is used as\nminimum for the width, when \"adjust_min_width\" (or,\n\"adjust_table_min_width\", respectively) is set.\n\nThe rules for the calculation:\n\n1. If a widget has no children, it can choose a suitable value,\n   typically based on dw::core::Extremes::minWidth and\n   dw::core::Extremes::minWidthIntrinsic.\n2. A widget must calculate *adjustmentWidth* from *adjustmentWidth* of\n   its children.\n\n*Note:* An implementation of dw::core::Widget::getExtremesImpl may set\nthis value *after* calling dw::core::Widget::correctExtremesOfChild,\nso that it cannot be used for the correction of extremes. In this case\n*useAdjustmentWidth = false* should be passed to\ndw::core::Widget::correctExtremesOfChild. On the other hand, if known\nbefore, *useAdjustmentWidth* should be set to *true*.\n\nRules for *new* methods related to resizing\n===========================================\n\n- Of course, *sizeRequestImpl* may (should) call *correctRequisition*,\n  and *getExtremesImpl* may (should) call *correctExtremes*.\n\n- *sizeRequestImpl* (and *correctRequisition*) is allowed to call\n  *getAvailWidth* and *getAvailHeight* with *forceValue* set, but\n  *getExtremesImpl* (and *correctExtremes*) is allowed to call these\n  only with *forceValue* unset.\n\n- For this reason, *sizeRequestImpl* is indeed allowed to call\n  *getExtremes* (dw::Table does so), but the opposite\n  (*getExtremesImpl* calling *sizeRequest*) is not allowed\n  anymore. (Before GROWS, the standard implementation\n  dw::core::Widget::getExtremesImpl did so.)\n\n- Finally, *getAvailWidth* and *getAvailHeight* may call\n  *getExtremes*, if and only if *forceValue* is set.\n\nHere is a diagram showing all permitted dependencies:\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10, color=\"#c0c0c0\"];\n   edge [arrowhead=\"open\", arrowtail=\"none\", color=\"#404040\"];\n\n   \"sizeRequest[Impl]\" -> \"getExtremes[Impl]\";\n   \"sizeRequest[Impl]\" -> correctRequisition;\n   \"getExtremes[Impl]\" -> correctExtremes;\n   \"sizeRequest[Impl]\" -> \"getAvail[Width|Height] (true)\";\n   \"getExtremes[Impl]\" -> \"getAvail[Width|Height] (false)\";\n   correctRequisition -> \"getAvail[Width|Height] (true)\";\n   correctExtremes -> \"getAvail[Width|Height] (false)\";\n   \"getAvail[Width|Height] (true)\" -> \"getExtremes[Impl]\";   \n}\n\\enddot\n\nOpen issues\n===========\n\n**Do CSS size dimensions override intrinsic sizes in all cases?** If a\ntextblock needs at least, say, 100 pixels width so that the text can\nbe read, but has a specification \"width: 50px\", should half of the\ntext be invisible? Or should the width be corrected again to 100\npixels?\n\nCurrently, in the CSS size specification is honoured in all cases,\nwith one exception: see dw::Textblock::sizeRequestImpl and see\ndw::Textblock::getExtremesImpl (the time when\ndw::core::Widget::correctRequisition and\ndw::core::Widget::correctExtremes, respectively, is called).\n\n*Not* honouring the CSS size specification in all cases could improve\nreadability in some cases, so this could depend on a user preference.\n\n**Update:** There is now a dillorc option <tt>adjust_min_width</tt>,\nwhich is implemented for widths, but not heights (since it is based on\nwidth extremes, but there are currently no height extremes).\n\nAnother problem is that in most cases, there is no clippping, so that\ncontents may exceed the allocation of the widget, but redrawing is not\nnecessarily triggered.\n\n**Percentage values for margins and paddings, as well as negative\nmargins** are interesting applications, but have not been considered\nyet. For negative margins, a new attribute\ndw::core::Widget::extraSpace could solve the problem of widgets\nsticking out of the allocation of parent.\n\n**Clarify percentage heights.** Search in widget.cc, and compare\nsection 10.5 ('height') of the CSS 2.1 specification to section 10.2\n('width').\n\n**Fast queue resize does not work fully.** Example: run\n*test/dw-simple-container-test* (dw_simple_container_test.cc), resize\n(best maximize) the window and follow (e.&nbsp;g. by using RTFL) what\nhappens in consequence of dw::core::Layout::viewportSizeChanged. The\ndw::SimpleContainer in the middle is not affected, so only the two\ndw::Textblock's (at the top and at the bottom) call queueResize with\n*fast = true*, and so get *NEEDS_RESIZE* set; but since it is not set\nfor the dw::SimpleContainer, *sizeRequest* is never called for the\nbottom dw::Textblock.\n\nThere does not seem to be a real case for this problem in dillo, since\nall widgets which may contain other widgets (except\ndw::SimpleContainer, which is not used outside tests) use the\navailable width and height (dw::core::Widget::usesAvailWidth and\ndw::core::Widget::usesAvailHeight), and so are always affected by\nviewport size changes.\n\n*/\n"
  },
  {
    "path": "devdoc/dw-images-and-backgrounds.doc",
    "content": "/** \\page dw-images-and-backgrounds Images and Backgrounds in Dw\n\nImage Buffers\n=============\n\nRepresentation of the image data is done by dw::core::Imgbuf, see\nthere for details. Drawing is done by dw::core::View\n(dw::core::View::drawImage).\n\nSince dw::core::Imgbuf provides memory management based on reference\ncounting, there may be an 1-to-n relation from image renderers (image\nwidgets or backgrounds, see below) and dw::core::Imgbuf. Since\ndw::core::Imgbuf does not know about renderers, but just provides\nrendering functionality, the caller must (typically after calling\ndw::core::Imgbuf::copyRow) notify all renderers connected to the\nbuffer.\n\n\nImage Renderer\n==============\n\nGenerally, there are no restrictions on how to manage\ndw::core::Imgbuf; but to handle image data from web resources, the\ninterface dw::core::ImgRenderer should be implemented. It is again\nwrapped by DilloImage (to make access from the C part possible, since\ndw::core::ImgRenderer is written in C++), which is referenced by\nDilloWeb. There are two positions where retrieving image data is\ninitiated:\n\n- Html_load_image: for embedded images (implemented by dw::Image,\n  which implements dw::core::ImgRenderer);\n- StyleEngine::apply (search for \"case\n  CSS_PROPERTY_BACKGROUND_IMAGE\"): for backgrond images; there are\n  some implementations of dw::core::ImgRenderer within the context of\n  dw::core::style::StyleImage.\n\nBoth are described in detail below. Notice that the code is quite\nsimilar; only the implementation of dw::core::ImgRenderer differs.\n\nAt this time, dw::core::ImgRenderer has got two methods (see more\ndocumentation there):\n\n- dw::core::ImgRenderer::setBuffer,\n- dw::core::ImgRenderer::drawRow,\n- dw::core::ImgRenderer::finish, and\n- dw::core::ImgRenderer::fatal.\n\n\nImages\n======\n\nThis is the simplest renderer, displaying an image. For each row to be\ndrawn,\n\n- first dw::core::Imgbuf::copyRow, and then\n- for each dw::Image, dw::Image::drawRow must be called, with the same\n  argument (no scaling is necessary).\n\ndw::Image automatically scales the dw::core::Imgbuf, the root buffer\nshould be passed to dw::Image::setBuffer.\n\n\\see dw::Image for more details.\n\n\nBackground Images\n=================\n\nSince background images are style resources, they are associated with\ndw::core::style::Style, as dw::core::style::StyleImage, which is\nhandled in a similar way (reference counting etc.) as\ndw::core::style::Color and dw::core::style::Font, although it is\nconcrete and not platform-dependant.\n\nThe actual renderer (passed to Web) is an instance of\ndw::core::ImgRendererDist (distributes all calls to a set of other\ninstances of dw::core::ImgRenderer), which contains two kinds of\nrenderers:\n\n- one instance of dw::core::style::StyleImage::StyleImgRenderer, which\n  does everything needed for dw::core::style::StyleImage, and\n- other renderers, used externally (widgets etc.), which are added by\n  dw::core::style::StyleImage::putExternalImgRenderer (and removed by\n  dw::core::style::StyleImage::removeExternalImgRenderer).\n\nThis diagram gives an comprehensive overview:\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"open\", dir=\"both\", arrowtail=\"none\",\n         labelfontname=Helvetica, labelfontsize=10, color=\"#404040\",\n         labelfontcolor=\"#000080\"];\n   fontname=Helvetica; fontsize=10;\n\n   subgraph cluster_dw_style {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n\n      Style [URL=\"\\ref dw::core::style::Style\"];\n      StyleImage [URL=\"\\ref dw::core::style::StyleImage\"];\n      Imgbuf [URL=\"\\ref dw::core::Imgbuf\", color=\"#a0a0a0\"];\n      StyleImgRenderer\n         [URL=\"\\ref dw::core::style::StyleImage::StyleImgRenderer\"];\n      ImgRenderer [URL=\"\\ref dw::core::ImgRenderer\", color=\"#ff8080\"];\n      ImgRendererDist [URL=\"\\ref dw::core::ImgRendererDist\"];\n      ExternalImgRenderer\n         [URL=\"\\ref dw::core::style::StyleImage::ExternalImgRenderer\",\n          color=\"#a0a0a0\"];\n      ExternalWidgetImgRenderer\n         [URL=\"\\ref dw::core::style::StyleImage::ExternalWidgetImgRenderer\",\n          color=\"#a0a0a0\"];\n   }\n\n   subgraph cluster_dw_layout {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n\n      Layout [URL=\"\\ref dw::core::Layout\"];\n      LayoutImgRenderer [URL=\"\\ref dw::core::Layout::LayoutImgRenderer\"];\n   }\n\n   subgraph cluster_dw_widget {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n\n      Widget [URL=\"\\ref dw::core::Widget\", color=\"#a0a0a0\"];\n      WidgetImgRenderer [URL=\"\\ref dw::core::Widget::WidgetImgRenderer\"];\n   }\n\n   subgraph cluster_dw_textblock {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n\n      Textblock [URL=\"\\ref dw::Textblock\"];\n      Word [URL=\"\\ref dw::Textblock::Word\"];\n      WordImgRenderer [URL=\"\\ref dw::Textblock::WordImgRenderer\"];\n      SpaceImgRenderer [URL=\"\\ref dw::Textblock::SpaceImgRenderer\"];\n   }\n\n   Style -> StyleImage [headlabel=\"*\", taillabel=\"1\"];\n   StyleImage -> Imgbuf [headlabel=\"*\", taillabel=\"1\"];\n   StyleImage -> StyleImgRenderer [headlabel=\"1\", taillabel=\"1\"];\n   StyleImage -> ImgRendererDist [headlabel=\"1\", taillabel=\"1\"];\n   ImgRendererDist -> StyleImgRenderer [headlabel=\"1\", taillabel=\"1\"];\n   ImgRendererDist -> ImgRenderer [headlabel=\"1\", taillabel=\"*\"];\n\n   ImgRenderer -> ImgRendererDist [arrowhead=\"none\", arrowtail=\"empty\",\n                                   dir=\"both\", style=\"dashed\"];\n   ImgRenderer -> StyleImgRenderer [arrowhead=\"none\", arrowtail=\"empty\",\n                                   dir=\"both\", style=\"dashed\"];\n   ImgRenderer -> ExternalImgRenderer [arrowhead=\"none\", arrowtail=\"empty\",\n                                      dir=\"both\", style=\"dashed\"];\n   ExternalImgRenderer -> ExternalWidgetImgRenderer [arrowhead=\"none\",\n                                 arrowtail=\"empty\", dir=\"both\", style=\"dashed\"];\n\n   Layout -> LayoutImgRenderer [headlabel=\"1\", taillabel=\"0..1\"];\n   ExternalImgRenderer -> LayoutImgRenderer [arrowhead=\"none\",\n                                 arrowtail=\"empty\", dir=\"both\", style=\"dashed\"];\n\n   Widget -> WidgetImgRenderer [headlabel=\"1\", taillabel=\"0..1\"];\n   ExternalWidgetImgRenderer -> WidgetImgRenderer [arrowhead=\"none\",\n                                 arrowtail=\"empty\", dir=\"both\", style=\"dashed\"];\n\n   Textblock -> Word [headlabel=\"1\", taillabel=\"*\"];\n   Word -> WordImgRenderer [headlabel=\"1\", taillabel=\"0..1\"];\n   Word -> SpaceImgRenderer [headlabel=\"1\", taillabel=\"0..1\"];\n   ExternalWidgetImgRenderer -> WordImgRenderer [arrowhead=\"none\",\n                                 arrowtail=\"empty\", dir=\"both\", style=\"dashed\"];\n   WordImgRenderer -> SpaceImgRenderer [arrowhead=\"none\", arrowtail=\"empty\",\n                                        dir=\"both\", style=\"dashed\"];\n}\n\\enddot\n\n<center>[\\ref uml-legend \"legend\"]</center>\n\n\nMemory management\n-----------------\n\ndw::core::style::StyleImage extends lout::signal::ObservedObject, so\nthat deleting this instance can be connected to code dealing with\ncache clients etc. See StyleImageDeletionReceiver and how it is\nattached in StyleEngine::apply (\"case CSS_PROPERTY_BACKGROUND_IMAGE\").\n\n\nBugs and Things Needing Improvement\n===================================\n\n(Mostly related to image backgrounds, when not otherwise mentioned.)\n\nHigh Priority\n-------------\n\n**Configurability, security/privacy aspects, etc.,** which are\ncurrently available for image widgets, should be adopted. Perhaps some\nmore configuration options specially for background images.\n\n\nMedium Priority\n---------------\n\n**Background-attachment** is not yet implemented, and will be postponed.\n\n**Calls to dw::core::ImgRenderer::fatal** are incomplete. As an\nexample, it is not called, when connecting to a server fails. (And so,\nas far as I see, no cache client is started.)\n\n\nLow Priority\n------------\n\n**Alpha support:** (not related to image backgrounds) currently alpha\nsupport (and also colormap management) is done in dicache, while\ndw::Image is only created with type RGB. This leads to several problems:\n\n- One dicache entry (representing an image related to the same URL),\n  which has only one background color, may refer to different images\n  with different background colors.\n- The dicache only handles background colors, not background images.\n\nThe solution is basicly simple: keep alpha support out of dicache;\ninstead implement RGBA in dw::Image. As it seems, the main problem is\nalpha support in FLTK/X11.\n\n\nSolved (Must Be Documented)\n---------------------------\n\n*Drawing background images row by row may become slow. As an\nalternative, dw::core::ImgRenderer::finish could be used. However,\ndrawing row by row could become an option.* There is now\ndw::core::style::drawBackgroundLineByLine, which can be changed in the\ncode, and is set to *false*. The old code still exists, so changing\nthis to *true* activates again drawing line by line.\n\n(For image widgets, this could also become an option: in contexts,\nwhen image data is retrieved in a very fast way.)\n\n*/\n"
  },
  {
    "path": "devdoc/dw-interrupted-drawing.doc",
    "content": "/** \\page dw-interrupted-drawing Interrupted drawing\n\nDescribing the problem\n======================\n\nWithout interrupting drawing (which is described below), a widget can\ndefine the order in which its parts (background, non-widget content,\nchild widgets, etc.) are drawn, but it must be drawn as a whole. There\nare situations when this is not possible.\n\nConsider the following simple HTML document:\n\n    <head>\n      <style>\n\t#sc-1 { position: relative; z-index: 1; background: #ffe0e0; }\n\t#fl-1 { float: right; background: #b0ffb0; }\n\t#sc-2 { position: relative; z-index: 1; background: #f0f0ff; }\n      </style>\n    </head>\n    <body>\n      <div id=\"sc-1\">\n\t<div id=\"fl-1\">\n\t  Float, line 1/3<br/>\n\t  Float, line 2/3<br/>\n\t  Float, line 3/3\n\t</div>\n\tStacking Context 1 \n\t<div id=\"sc-2\">Stacking Context 2</div>\n      </div>\n    </body>\n\nThe rendering will look like this:\n\n\\image html dw-interrupted-drawing-1.png\n\nNote the missing \"Float, line 2/3\" of element #fl-1, which is covered\nby element #sc-2.\n\nAs described in \\ref dw-out-of-flow, it has to be distinguished\nbetween the *container* hierarchy (equivalent to the hierarchy of\ndw::core::Widget.) and the the *generator* hierarchy. In the following\ndiagram, the former is represented by solid lines, the latter by\ndotted lines:\n\n\\dot\ndigraph G {\n   node [shape=rect, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"vee\"];\n\n   \"#sc-1\" [fillcolor=\"#ffe0e0\", style=\"filled\"];\n   \"#fl-1\" [fillcolor=\"#b0ffb0\", style=\"filled\"];\n   \"#sc-2\" [fillcolor=\"#f0f0ff\", style=\"filled\"];\n\n   \"body\" -> \"#sc-1\";\n   \"body\" -> \"#fl-1\";\n   { rank=same; \"#sc-1\" -> \"#fl-1\" [style=dotted]; }\n   \"#sc-1\" -> \"#sc-2\";\n}\n\\enddot\n\n\nThe drawing order of the four elements (represented by widgets) is:\n\n- body,\n- #sc-1,\n- #fl-1,\n- #sc-2.\n\nSince \n\n1. #sc-2 is a child of #sc-1, but\n2. #fl-1 is a child of the body, and\n3. a widget can only draw its descendants (not neccessary children,\n   but drawing siblings is not allowed),\n\n#sc-1 cannot be drawn as a whole; instead drawing is **interrupted**\nby #fl-1. This means:\n\n1. the background and text of #sc-1 is drawn;\n2. drawing of #sc-1 is **interrupted** by #fl-1 (see below for details),\n3. drawing of #sc-1 is **continued**, by drawing #sc-2.\n\nThe exact control flow is described in this sequence diagram:\n\n\\image html dw-interrupted-drawing-2.png\n\n\nWhen is drawing interrupted?\n============================\n\nA widget out of flow is regarded as part of the stacking context (see\n\\ref dw-stacking-context) of its *generator* (in the example above:\n#fl-1 is part of the stacking context stablished by #sc-1, not the one\nestablished by body). For this reason, a widget out of flow must, in\nsome cases, drawn while the *gerator* is drawn, as an\ninterruption. The exact rule:\n\nA widget out of flow must be drawn as an interruption (while the\n*generator* is drawn) if the stacking context of the generator (to\nwhich this widget belongs) is in front of the stacking context of the\ncontainer (the parent widget).\n\nSee dw::oof::OOFAwareWidget::doesWidgetOOFInterruptDrawing.\n\n\nHow does interruption of drawing work?\n======================================\n\nWhen a widget detects that an other widget should be drawn as\ninterruption (see above), it calls dw::core::Widget::drawInterruption,\nwhich\n\n1. draws the widget within another \"context\" (area and reference\n   widget); for this the original drawing area\n   (dw::core::DrawingContext::getToplevelArea) is used.\n2. Using dw::core::DrawingContext::addWidgetDrawnAsInterruption, and\n   checking later with\n   dw::core::DrawingContext::hasWidgetBeenDrawnAsInterruption prevents\n   these widgets from being drawn twice.\n\n*/\n"
  },
  {
    "path": "devdoc/dw-layout-views.doc",
    "content": "/** \\page dw-layout-views Layout and Views\n\nRendering of Dw is done in a way resembling the model-view pattern, at\nleast formally.  Actually, the counterpart of the model, the layout\n(dw::core::Layout), does a bit more than a typical model, namely the\nlayouting (delegated to the widget tree, see \\ref dw-layout-widgets),\nand the view does a bit less than a typical view, i.e. only the actual\ndrawing.\n\nAdditionally, there is a structure representing common properties of\nthe platform. A platform is typically related to the underlying UI\ntoolkit, but other uses may be thought of.\n\nThis design helps to archieve two important goals:\n\n<ul>\n<li> Abstraction of the actual drawing, by different implementations\n     of dw::core::View.\n\n<li> It makes portability simple.\n</ul>\n\n\n<h2>Viewports</h2>\n\nAlthough the design implies that the usage of viewports should be\nfully transparent to the layout module, this cannot be fully achieved,\nfor the following reasons:\n\n<ul>\n<li> Some features, which are used on the level of dw::core::Widget,\n     e.g. anchors, refer to scrolling positions.\n\n<li> Size hints (see \\ref dw-layout-widgets) depend on the viewport\n     sizes, e.g. when the user changes the window size, and so also\n     the size of a viewport, the text within should be rewrapped.\n</ul>\n\nTherefore, dw::core::Layout keeps track of the viewport size, the\nviewport position, and even the thickness of the scrollbars, they are\nrelevant, see below for more details.\nIf a viewport is not used, however, the size is not defined.\n\nWhether a given dw::core::View implementation is a viewport or not, is\ndefined by the return value of dw::core::View::usesViewport. If this\nmethod returns false, the following methods need not to be implemented\nat all:\n\n<ul>\n<li> dw::core::View::getHScrollbarThickness,\n<li> dw::core::View::getVScrollbarThickness,\n<li> dw::core::View::scrollTo, and\n<li> dw::core::View::setViewportSize.\n</ul>\n\n<h3>Scrolling Positions</h3>\n\nThe scrolling position is the canvas position at the upper left corner\nof the viewport. Views using viewports must\n\n<ol>\n<li> change this value on request (dw::core::View::scrollTo), and\n<li> tell other changes to the layout, e.g. caused by user events\n     (dw::core::Layout::scrollPosChanged).\n</ol>\n\nApplications of scrolling positions (anchors, test search etc.) are\nhandled by the layout, in a way fully transparent to the view.\n\n<h3>Scrollbars</h3>\n\nA feature of the viewport size model are scrollbars. There may be a\nvertical scrollbar and a horizontal scrollbar, displaying the\nrelationship between canvas and viewport height or width,\nrespectively. If they are not needed, they are hidden, to save screen\nspace.\n\nSince scrollbars decrease the usable space of a view, dw::core::Layout\nmust know how much space they take. The view returns, via\ndw::core::View::getHScrollbarThickness and\ndw::core::View::getVScrollbarThickness, how thick they will be, when\nvisible.\n\nViewport sizes, which denote the size of the viewport widgets, include\nscrollbar thicknesses. When referring to the viewport \\em excluding\nthe scrollbars space, we will call it \"usable viewport size\", this is\nthe area, which is used to display the canvas.\n\n<h2>Drawing</h2>\n\nA view must implement several drawing methods, which work on the whole\ncanvas. If it is necessary to convert them (e.g. into\ndw::fltk::FltkViewport), this is done in a way fully transparent to\ndw::core::Widget and dw::core::Layout, instead, this is done by the\nview implementation.\n\nThere exist following situations:\n\n<ul>\n<li> A view gets an expose event: It will delegate this to the\n     layout (dw::core::Layout::draw), which will then pass it to the\n     widgets (dw::core::Widget::draw), with the view as a parameter.\n     Eventually, the widgets will call drawing methods of the view.\n\n<li> A widget requests a redraw: In this case, the widget will\n     delegate this to the layout (dw::core::Layout::queueDraw), which\n     delegates it to the view (dw::core::View::queueDraw).\n     Typically, the view will queue these requests for efficiency.\n\n<li> A widget requests a resize: This case is described below, in short,\n     dw::core::View::queueDrawTotal is called for the view.\n</ul>\n\nIf the draw method of a widget is implemented in a way that it may\ndraw outside of the widget's allocation, it should draw into a\n<i>clipping view.</i> A clipping view is a view related to the actual\nview, which guarantees that the parts drawn outside are discarded. At\nthe end, the clipping view is merged into the actual view. Sample\ncode:\n\n\\code\nvoid Foo::draw (dw::core::View *view, dw::core::Rectangle *area)\n{\n   // 1. Create a clipping view.\n   dw::core::View clipView =\n      view->getClippingView (allocation.x, allocation.y,\n                             allocation.width, getHeight ());\n\n   // 2. Draw into clip_view\n   clipView->doSomeDrawing (...);\n\n   // 3. Draw the children, they receive the clipping view as argument.\n   dw::core::Rectangle *childArea\n   for (<all relevant children>) {\n      if (child->intersects (area, &childArea))\n         child->draw (clipView, childArea);\n   }\n\n   // 4. Merge\n   view->mergeClippingView (clipView);\n}\n\\endcode\n\nA drawing process is always embedded into calls of\ndw::core::View::startDrawing and dw::core::View::finishDrawing. An\nimplementation of this may e.g. use backing pixmaps, to prevent\nflickering.\n\n\n<h2>Sizes</h2>\n\nIn the simplest case, the view does not have any influence on\nthe canvas size, so it is told about changes of the\ncanvas size by a call to dw::core::View::setCanvasSize. This happens\nin the following situations:\n\n<ul>\n<li> dw::core::Layout::addWidget,\n<li> dw::core::Layout::removeWidget (called by dw::core::Widget::~Widget),\n     and\n<li> dw::core::Layout::queueResize (called by\n     dw::core::Widget::queueResize, when a widget itself requests a size\n     change).\n</ul>\n\n<h3>Viewports</h3>\n\nThere are two cases where the viewport size changes:\n\n<ul>\n<li> As an reaction on a user event, e.g. when the user changes the\n     window size. In this case, the view delegates this\n     change to the layout, by calling\n     dw::core::Layout::viewportSizeChanged.\n\n<li> The viewport size may also depend on the visibility of UI\n     widgets, which depend on the world size, e.g scrollbars,\n     generally called \"viewport markers\". This is described in a separate\n     section.\n</ul>\n\nAfter the creation of the layout, the viewport size is undefined. When\na view is attached to a layout, and this view can already specify\nits viewport size, it may call\ndw::core::Layout::viewportSizeChanged within the implementation of\ndw::core::Layout::setLayout. If not, it may do this as soon as the\nviewport size is known.\n\nGenerally, the scrollbars have to be considered. If e.g. an HTML page\nis rather small, it looks like this:\n\n\\image html dw-viewport-without-scrollbar.png\n\nIf some more data is retrieved, so that the height exceeds the\nviewport size, the text has to be rewrapped, since the available width\ngets smaller, due to the vertical scrollbar:\n\n\\image html dw-viewport-with-scrollbar.png\n\nNotice the different line breaks.\n\nThis means circular dependencies between these different sizes:\n\n<ol>\n<li> Whether the scrollbars are visible or not, determines the\n     usable space of the viewport.\n\n<li> From the usable space of the viewport, the size hints for the\n     toplevel are calculated.\n\n<li> The size hints for the toplevel widgets may have an effect on its\n     size, which is actually the canvas size.\n\n<li> The canvas size determines the visibility of the scrollbarss.\n</ol>\n\nTo make an implementation simpler, we simplify the model:\n\n<ol>\n<li> For the calls to dw::core::Widget::setAscent and\n     dw::core::Widget::setDescent, we will always exclude the\n     horizontal scrollbar thickness (i.e. assume the horizontal\n     scrollbar is used, although the visibility is determined correctly).\n\n<li> For the calls to dw::core::Widget::setWidth, we will calculate\n     the usable viewport width, but with the general assumption, that\n     the widget generally gets higher.\n</ol>\n\nThis results in the following rules:\n\n<ol>\n<li> Send always (when it changes) dw::core::Layout::viewportHeight\n     minus the maximal value of dw::core::View::getHScrollbarThickness as\n     argument to dw::core::Widget::setAscent, and 0 as argument to\n     dw::core::Widget::setDescent.\n\n<li> There is a flag, dw::core::Layout::canvasHeightGreater, which is set\n     to false in the following cases:\n\n     <ul>\n     <li> dw::core::Layout::addWidget,\n     <li> dw::core::Layout::removeWidget (called by dw::core::Widget::~Widget),\n          and\n     <li> dw::core::Layout::viewportSizeChanged.\n     </ul>\n\n     Whenever the canvas size is calculated (dw::core::Layout::resizeIdle),\n     and dw::core::Layout::canvasHeightGreater is false, a test is made,\n     whether the widget has in the meantime grown that high, that the second\n     argument should be set to true (i.e. the vertical scrollbar gets visible).\n     As soon as and dw::core::Layout::canvasHeightGreater is true, no such test\n     is done anymore.\n</ol>\n\n*/\n"
  },
  {
    "path": "devdoc/dw-layout-widgets.doc",
    "content": "/** \\page dw-layout-widgets Layout and Widgets\n\nBoth, the layouting and the drawing is delegated to a tree of\nwidgets. A widget represents a given part of the document, e.g. a text\nblock, a table, or an image. Widgets may be nested, so layouting and\ndrawing may be delegated by one widget to its child widgets.\n\nWhere to define the borders of a widget, whether to combine different\nwidgets to one, or to split one widget into multiple ones, should be\nconsidered based on different concerns:\n\n<ul>\n<li> First, there are some restrictions of Dw:\n\n    <ul>\n    <li> The allocation (this is the space a widget allocates at\n         a time) of a dillo widget is always rectangular, and\n    <li> the allocation of a child widget must be a within the allocation\n         of the parent widget.\n    </ul>\n\n<li> Since some widgets are already rather complex, an important goal\n     is to keep the implementation of the widget simple.\n\n<li> Furthermore, the granularity should not be too fine, because of the\n     overhead each single widget adds.\n</ul>\n\nFor CSS, there will be a document tree on top of Dw, this will be\nflexible enough when mapping the document structure on the widget\nstructure, so you should not have the document structure in mind.\n\n<h2>Sizes</h2>\n\n\\ref dw-widget-sizes\n\n\n<h2>Styles</h2>\n\nEach widget is assigned a style, see dw::core::style for more\ninformations.\n\n\n<h2>Iterators</h2>\n\nWidgets must implement dw::core::Widget::iterator. There are several\ncommon iterators:\n\n<ul>\n<li>dw::core::EmptyIterator, and\n<li>dw::core::TextIterator.\n</ul>\n\nBoth hide the constructor, use the \\em create method.\n\nThese simple iterators only iterate through one widget, it does not\nhave to iterate recursively through child widgets. Instead, the type\ndw::core::Content::WIDGET is returned, and the next call of\ndw::core::Iterator::next will return the piece of contents \\em after\n(not within) this child widget.\n\nThis makes implementation much simpler, for recursive iteration, there\nis dw::core::DeepIterator.\n\n\n<h2>Anchors and Scrolling</h2>\n\n\\todo This section is not implemented yet, after the implementation,\n  the documentation should be reviewed.\n\nHere is a description, what is to be done for a widget\nimplementation. How to jump to anchors, set scrolling positions\netc. is described in \\ref dw-usage.\n\n<h3>Anchors</h3>\n\nAnchors are position markers, which are identified by a name, which is\nunique in the widget tree. The widget must care about anchors in three\ndifferent situations:\n\n<ol>\n<li> Adding an anchor is inititiated by a specific widget method, e.g.\n     dw::Textblock::addAnchor. Here, dw::core::Widget::addAnchor must be\n     called,\n\n<li> Whenever the position of an anchor is changed,\n     dw::core::Widget::changeAnchor is called (typically, this is done\n     in the implementation of dw::core::Widget::sizeAllocateImpl).\n\n<li> When a widget is destroyed, the anchor must be removed, by calling\n     dw::core::Widget::removeAnchor.\n</ol>\n\nAll these methods are delegated to dw::core::Layout, which manages\nanchors centrally. If the anchor in question has been set to jump to,\nthe viewport position is automatically adjusted, see \\ref\ndw-usage.\n\n\n<h2>Drawing</h2>\n\nIn two cases, a widget has to be drawn:\n\n<ol>\n<li> as a reaction on an expose event,\n<li> if the widget content has changed and it needs to be redrawn.\n</ol>\n\nIn both cases, drawing is done by the implementation of\ndw::core::Widget::draw, which draws into the view.\n\n\nEach view provides some primitive methods for drawing, most should be\nobvious. Note that the views do not know anything about dillo\nwidgets, and so coordinates have to be passed as canvas coordinates.\n\nA widget may only draw in its own allocation. If this cannot be\nachieved, a <i>clipping view</i> can be used, this is described in\n\\ref dw-layout-views. Generally, drawing should then look like:\n\n\\code\nvoid Foo::draw (dw::core::View *view, dw::core::Rectangle *area)\n{\n   // 1. Create a clipping view.\n   dw::core::View clipView =\n      view->getClippingView (allocation.x, allocation.y,\n                             allocation.width, getHeight ());\n\n   // 2. Draw into clip_view\n   clipView->doSomeDrawing (...);\n\n   // 3. Draw the children, they receive the clipping view as argument.\n   dw::core::Rectangle *childArea\n   for (<all relevant children>) {\n      if (child->intersects (area, &childArea))\n         child->draw (clipView, childArea);\n   }\n\n   // 4. Merge\n   view->mergeClippingView (clipView);\n}\n\\endcode\n\nClipping views are expensive, so they should be avoided when possible.\n\nThe second argument to dw::core::Widget::draw is the region, which has\nto be drawn. This may (but needs not) be used for optimization.\n\nIf a widget contains child widgets, it must explicitly draw these\nchildren (see also code example above). For this, there is the useful\nmethod dw::core::Widget::intersects, which returns, which area of the\nchild must be drawn.\n\n<h3>Explicit Redrawing</h3>\n\nIf a widget changes its contents, so that it must be redrawn, it must\ncall dw::core::Widget::queueDrawArea or\ndw::core::Widget::queueDraw. The first variant expects a region within\nthe widget, the second will cause the whole widget to be redrawn. This\nwill cause an asynchronous call of dw::core::Widget::draw.\n\nIf only the size changes, a call to dw::core::Widget::queueResize is\nsufficient, this will also queue a complete redraw (see \\ref\ndw-widget-sizes.)\n\n\n<h2>Mouse Events</h2>\n\nA widget may process mouse events. The view (\\ref dw-layout-views)\npasses mouse events to the layout, which then passes them to the\nwidgets. There are two kinds of mouse events:\n\n<ul>\n<li>events returning bool, and\n<li>events returning nothing (void).\n</ul>\n\nThe first group consists of:\n\n<ul>\n<li>dw::core::Widget::buttonPressImpl,\n<li>dw::core::Widget::buttonReleaseImpl, and\n<li>dw::core::Widget::motionNotifyImpl.\n</ul>\n\nFor these events, a widget returns a boolean value, which denotes,\nwhether the widget has processed this event (true) or not (false). In\nthe latter case, the event is delegated according to the following\nrules:\n\n<ol>\n<li> First, this event is passed to the bottom-most widget, in which\n     allocation the mouse position is in.\n<li> If the widget does not process this event (returning false), it is\n     passed to the parent, and so on.\n<li> The final result (whether \\em any widget has processed this event) is\n     returned to the view.\n</ol>\n\nThe view may return this to the UI toolkit, which then interprets this\nin a similar way (whether the viewport, a UI widget, has processed\nthis event).\n\nThese events return nothing:\n\n<ul>\n<li>dw::core::Widget::enterNotifyImpl and\n<li>dw::core::Widget::leaveNotifyImpl.\n</ul>\n\nsince they are bound to a widget.\n\nWhen processing mouse events, the layout always deals with two\nwidgets: the widget, the mouse pointer was in, when the previous mouse\nevent was processed, (below called the \"old widget\") and the widget,\nin which the mouse pointer is now (\"new widget\").\n\nThe following paths are calculated:\n\n<ol>\n<li> the path from the old widget to the nearest common ancestor of the old\n     and the new widget, and\n<li> the path from this ancestor to the new widget.\n</ol>\n\nFor the widgets along these paths, dw::core::Widget::enterNotifyImpl\nand dw::core::Widget::leaveNotifyImpl are called.\n\n<h3>Signals</h3>\n\nIf a caller outside of the widget is interested in these events, he\ncan connect a dw::core::Layout::LinkReceiver. For those events with a\nboolean return value, the results of the signal emission is regarded,\ni.e. the delegation of an event to the parent of the widget can be\nstopped by a signal receiver returning true, even if the widget method\nreturns false.\n\nFirst, the widget method is called, then (in any case) the signal is\nemitted.\n\n<h3>Selection</h3>\n\nIf your widget has selectable contents, it should delegate the events\nto dw::core::SelectionState (dw::core::Layout::selectionState).\n\n\n<h2>Miscellaneous</h2>\n\n<h3>Cursors</h3>\n\nEach widget has a cursor, which is set by\ndw::core::Widget::setCursor. If a cursor is assigned to a part of a\nwidget, this widget must process mouse events, and call\ndw::core::Widget::setCursor explicitly.\n\n(This will change, cursors should become part of\ndw::core::style::Style.)\n\n<h3>Background</h3>\n\nBackgrounds are part of styles\n(dw::core::style::Style::backgroundColor). If a widget assigns\nbackground colors to parts of a widget (as dw::Table does for rows),\nit must call dw::core::Widget::setBgColor for the children inside this\npart.\n\n*/\n"
  },
  {
    "path": "devdoc/dw-line-breaking.doc",
    "content": "/** \\page dw-line-breaking Changes in Line-Breaking and Hyphenation\n\n<div style=\"border: 2px solid #ffff00; margin-bottom: 0.5em;\npadding: 0.5em 1em; background-color: #ffffe0\"><b>Info:</b>\nShould be incorporated into dw::Textblock.</div>\n\nIntroduction\n============\n\nFor the implementation of hyphenation in dillo, not only a\nhyphenation algorithm was implemented, but also, the line breaking was\nchanged to a simple optimization per line. Aside from the improvement\nby this change per se, an important aspect is the introduction of\n\"penalties\". Before this change, dillo put all words into a line which\nfitted into it; now, a \"badness\" is calculated for a possible\nbreakpoint, and the best breakpoint, i.&nbsp;e. the breakpoint with the\nsmallest value for \"badness\", is chosen. This can be simply refined\nto define \"good\" and \"bad\" breakpoints by assigning a \"penalty\"; the\nbest breakpoint is then the one with the smallest value of \"badness +\npenalty\". Details can be found below.\n\nExample: Normal spaces have a penalty of 0, while hyphenation points\nget a penalty of, say, 1, since hyphenation is generally considered as\na bit \"ugly\" and should rather be avoided. Consider a situation where\nthe word \"dillo\" could be hyphenated, with the following badnesses:\n\n- before \"dillo\": 0.6;\n- between \"dil-\" and \"lo\": 0.2;\n- after \"dillo\": 0.5.\n\nSince the penalty is added, the last value is the best one, so \"dillo\"\nis put at the end of the line, without hyphenation.\n\nUnder other circumstances (e.&nbsp;g. narrower lines), the values\nmight be different:\n\n- before \"dillo\": infinite;\n- between \"dil-\" and \"lo\": 0.3;\n- after \"dillo\": 1.5.\n\nIn this case, even the addition of the penalty makes hyphenation the\nbest choice.\n\n\nLiterature\n==========\n\nBreaking Paragraphs Into Lines\n------------------------------\n\nAlthough dillo does not (yet?) implement the algorithm T<sub>E</sub>X\nuses for line breaking, this document shares much of the notation used\nby the article *Breaking Paragraphs Into Lines* by Donald E. Knuth and\nMichael F. Plass; originally published in: Software -- Practice and\nExperience **11** (1981), 1119-1184; reprinted in: *Digital\nTypography* by Donalt E. Knuth, CSLI Publications 1999.  Anyway an\ninteresting reading.\n\nHyphenation\n-----------\n\nDillo uses the algorithm by Frank Liang, which is described in his\ndoctoral dissertation found at http://www.tug.org/docs/liang/. There\nis also a description in chapter H (\"Hyphenation\") of *The\nT<sub>E</sub>Xbook* by Donald E. Knuth, Addison-Wesley 1984.\n\nPattern files can be found at\nhttp://www.ctan.org/tex-archive/language/hyphenation.\n\n\nOverview of Changes\n===================\n\nStarting with this change, dw/textblock.cc has been split up; anything\nrelated to line breaking has been moved into\ndw/textblock_linebreaking.cc. This will also be done for other aspects\nlike floats. (Better, however, would be a clean logical split.)\n\nAn important change relates to the way that lines are added: before,\ndillo would add a line as soon as a new word for this line was\nadded. Now, a line is added not before the *last* word of this line is\nknown. This has two important implications:\n\n- Some values in dw::Textblock::Line, which represented values\n  accumulated within the line, could be removed, since now, these\n  values can be calculated simply in a loop.\n- On the other hand, this means that some words may not belong to any\n  line. For this reason, in some cases (e.&nbsp;g. in\n  dw::Textblock::sizeRequestImpl) dw::Textblock::showMissingLines is\n  called, which creates temporary lines, which must, under other\n  circumstances, be removed again by\n  dw::Textblock::removeTemporaryLines, since they have been created\n  based on limited information, and so possibly in a wrong way. (See\n  below for details.)\n\nWhen a word can be hyphenated, an instance of dw::Textblock::Word is\nused for each part. Notice that soft hyphens are evaluated\nimmediately, but automatic hyphenation is done in a lazy way (details\nbelow), so the number of instances may change. There are some new\nattributes: only when dw::Textblock::Word::canBeHyphenated is set to\n*true*, automatic hyphenation is allowed; it is set to false when soft\nhyphens are used for a word, and (of course) by the automatic\nhyphenation itself. Furthermore, dw::Textblock::Word::hyphenWidth\n(more details in the comment there) has to be included when\ncalculating line widths.\n\nSome values should be configurable: dw::Textblock::HYPHEN_BREAK, the\npenalty for hyphens. Also dw::Textblock::Word::stretchability,\ndw::Textblock::Word::shrinkability, which are both set in\ndw::Textblock::addSpace.\n\n\nCriteria for Line-Breaking\n==========================\n\nBefore these changes to line breaking, a word (represented by\ndw::Textblock::Word) had the following attributes related to\nline-breaking:\n\n- the width of the word itself, represented by\n  dw::Textblock::Word::size;\n- the width of the space following the word, represented by\n  dw::Textblock::Word::origSpace.\n\nIn a more mathematical notation, the \\f$i\\f$th word has a width\n\\f$w_i\\f$ and a space \\f$s_i\\f$.\n\nA break was possible, when there was a space between the two words,\nand the first possible break was chosen.\n\nWith hyphenation, the criteria are refined. Hyphenation should only be\nused when otherwise line breaking results in very large spaces. We\ndefine:\n\n- the badness \\f$\\beta\\f$ of a line, which is greater the more the\n  spaces between the words differ from the ideal space;\n- a penalty \\f$p\\f$ for any possible break point.\n\nThe goal is to find those break points, where \\f$\\beta + p\\f$ is\nminimal.\n\nExamples for the penalty \\f$p\\f$:\n\n- 0 for normal line breaks (between words);\n- \\f$\\infty\\f$ to prevent a line break at all costs;\n- \\f$-\\infty\\f$ to force a line\n- a positive, but finite, value for hyphenation points.\n\nSo we need the following values:\n\n- \\f$w_i\\f$ (the width of the word \\f$i\\f$ itself);\n- \\f$s_i\\f$ (the width of the space following the word \\f$i\\f$);\n- the stretchability \\f$y_i\\f$, a value denoting how much the space\n  after word\\f$i\\f$ can be stretched (typically \\f${1\\over 2} s_i\\f$\n  for justified text; otherwise 0, since the spaces are not\n  stretched);\n- the shrinkability \\f$y_i\\f$, a value denoting how much the space\n  after word\\f$i\\f$ can be shrunken (typically \\f${1\\over 3} s_i\\f$\n  for justified text; otherwise 0, since the spaces are not shrinked);\n- the penalty \\f$p_i\\f$, if the line is broken after word \\f$i\\f$;\n- a width \\f$h_i\\f$, which is added, when the line is broken after\n  word \\f$i\\f$.\n\n\\f$h_i\\f$ is the width of the hyphen, if the word \\f$i\\f$ is a part of\nthe hyphenated word (except the last part); otherwise 0.\n\nLet \\f$l\\f$ be the (ideal) width (length) of the line, which is\ne.&nbsp;at the top given by the browser window width. Furthermore, all words\nfrom \\f$a\\f$ to \\f$b\\f$ are added to the line. \\f$a\\f$ is fixed: we do\nnot modify the previous lines anymore; but our task is to find a\nsuitable \\f$b\\f$.\n\nWe define:\n\n\\f[W_a^b = \\sum_{i=a}^{b} w_i + \\sum_{i=a}^{b-1} s_i + h_b\\f]\n\n\\f[Y_a^b = {Y_0}_a^b + \\sum_{i=a}^{b-1} y_i\\f]\n\n\\f[Z_a^b = {Z_0}_a^b + \\sum_{i=a}^{b-1} z_i\\f]\n\n\n\\f$W_a^b\\f$ is the total width, \\f$Y_a^b\\f$ the total stretchability,\nand \\f$Z_a^b\\f$ the total shrinkability. \\f${Y_0}_a^b\\f$ and\n\\f${Z_0}_a^b\\f$ are the stretchability and shrinkability defined per\nline, and applied at the borders; they are 0 for justified text, but\n\\f${Y_0}_a^b\\f$ has a positive value otherwise, see below for details.\n\nFurthermore the *adjustment ratio* \\f$r_a^b\\f$:\n\n- in the ideal case that \\f$W_a^b = l\\f$: \\f$r_a^b = 0\\f$;\n- if \\f$W_a^b < l\\f$: \\f$r_a^b = (l - W_a^b) / Y_a^b\\f$\n  (\\f$r_a^b < 0\\f$ in this case);\n- if \\f$W_a^b > l\\f$: \\f$r_a^b = (l - W_a^b) / Z_a^b\\f$\n  (\\f$r_a^b < 0\\f$ in this case).\n\nThe badness \\f$\\beta_a^b\\f$ is defined as follows:\n\n- if \\f$r_a^b\\f$ is undefined or \\f$r_a^b < -1\\f$: \\f$\\beta_a^b = \\infty\\f$;\n- otherwise: \\f$\\beta_a^b = |r_a^b|^3\\f$\n\nThe goal is to find the value of \\f$b\\f$ where \\f$\\beta_a^b + p_b\\f$\nis minimal. (\\f$a\\f$ is given, since we do not modify the previous\nlines.)\n\nAfter a couple of words, it is not predictable whether this minimum\nhas already been reached. There are two cases where this is possible\nfor a given \\f$b'\\f$:\n\n- \\f$\\beta_{b'}^a = \\infty\\f$ (line gets too tight):\n  \\f$a \\le b < b'\\f$, the minimum has to be searched between these two\n  values;\n- \\f$p_{b'} = -\\infty\\f$ (forced line break):\n  \\f$a \\le b \\le b'\\f$ (there may be another minimum of\n  \\f$\\beta_a^b\\f$ before; note the \\f$\\le\\f$ instead of \\f$<\\f$).\n\nThis leads to a problem that the last words of a text block are not\ndisplayed this way, since they do not fulfill these rules for being\nadded to a line. For this reason, there are \"temporary\" lines already\ndescribed above.\n\n(Note that the actual calculation differs from this description, since\ninteger arithmetic is used for performance, which make the actual\ncode more complicated. See dw::Textblock::BadnessAndPenalty for\ndetails.)\n\nRagged Borders\n--------------\n\nFor other than justified text (left-, right-aligned and centered), the\nspaces between the words are not shrinked or stretched (so \\f$y_i\\f$\nand \\f$z_i\\f$ are 0), but additional space is added to the left or\nright border or to both. For this reason, an additional stretchability\n\\f${Y_0}_a^b\\f$ is added (see definition above). Since this space at\nthe border is 0 in an ideal case (\\f$W_a^b = l\\f$), it cannot be\nshrunken, so \\f${Z_0}_a^b\\f$ is 0.\n\nThis is not equivalent to the calculation of the total stretchability\nas done for justified text, since in this case, the stretchability\ndepends on the number of words: consider the typical case that all\nspaces and stretchabilities are equal (\\f$y_a = y_{a + 1} = \\ldots =\ny_b\\f$). With \\f$n\\f$ words, the total strechability would be \\f$n\n\\cdot y_a\\f$, so increase with an increasing number of words\n(\\f$y_a\\f$ is constant). This is correct for justified text, but for\nother alignments, where only one space (or two, for centered text) is\nchanged, this would mean that a line with many narrow words is more\nstretchable than a line with few wide words.\n\nIt is obvious that left-aligned text can be handled in the same way as\nright-aligned text. [... Centered text? ...]\n\nThe default value for the stretchability is the line height without\nthe space between the lines (more precisely: the maximum of all word\nheights). The exact value not so important when comparing different\npossible values for the badness \\f$\\beta_a^b\\f$, when \\f${Y_0}_a^b\\f$\nis nearly constant for different \\f$b\\f$ (which is the case for the\nactual value), but it is important for the comparison with penalties,\nwhich are constant. To be considered is also that for non-justified\ntext, hyphenation is differently (less) desirable; this effect can be\nachieved by enlarging the stretchability, which will lead to a smaller\nbadness, and so make hyphenation less likely. The user can configure\nthe stretchability by changing the preference value\n*stretchability_factor* (default: 1.0).\n\n(Comparison to T<sub>E</sub>X: Knuth and Plass describe a method for\nragged borders, which is effectively the same as described here (Knuth\n1999, pp.&nbsp;93--94). The value for the stretchability of the line\nis slightly less, 1&nbsp;em (ibid., see also p.&nbsp;72 for the\ndefinition of the units). However, this article suggests a value for\nthe hyphenation penalty, which is ten times larger than the value for\njustified text; this would suggest a larger value for\n*stretchability_factor*.)\n\n\nHyphens\n=======\n\nWords (instances of dw::Textblock::Word), which are actually part of a\nhyphenated word, are always drawn as a whole, not seperately. This\nway, the underlying platform is able to apply kerning, ligatures, etc.\n\nCalculating the width of such words causes some problems, since it is\nnot required that the width of text \"AB\" is identical to the width of\n\"A\" plus the width of \"B\", just for the reasons mentioned above. It\ngets even a bit more complicated, since it is required that a word\npart (instance of dw::Textblock::Word) has always the same length,\nindependent of whether hyphenation is applied or not. Furthermore, the\nhyphen length is fixed for a word; for practical reasons, it is always\nthe width of a hyphen, in the given font.\n\nFor calculating the widths, consider a word of four syllables:\nA-B-C-D. There are 3 hyphenation points, and so 2<sup>3</sup> = 8\npossible ways of hyphenation: ABCD, ABC-D, AB-CD, AB-C-D, A-BCD,\nA-BC-D, A-B-CD, A-B-C-D. (Some of them, like the last one, are only\nprobable for very narrow lines.)\n\nLet w(A), w(B), w(C), w(D) be the word widths (part of\ndw::Textblock::Word::size), which have to be calculated, and l be a\nshorthand for dw::core::Platform::textWidth. Without considering\nthis problem, the calculation would be simple: w(A) = l(A)\netc. However, it gets a bit more complicated. Since all\nnon-hyphenations are drawn as a whole, the following conditions can be\nconcluded:\n\n- from drawing \"ABCD\" (not hyphenated at all): w(A) + w(B) + w(C) +\n  w(D) = l(ABCD);\n- from drawing \"BCD\", when hyphenated as \"A-BCD\" (\"A-\" is not\n  considered here): w(B) + w(C) + w(D) = l(BCD);\n- likewise, from drawing \"CD\" (cases \"AB-CD\" and \"A-B-CD\"): w(C) +\n  w(D) = l(CD);\n- finally, for the cases \"ABC-D\", \"AB-C-D\", \"A-BC-D\", and \"A-B-C-D\":\n  w(D) = l(D).\n\nSo, the calculation is simple:\n\n- w(D) = l(D)\n- w(C) = l(CD) - w(D)\n- w(B) = l(BCD) - (w(C) + w(D))\n- w(A) = l(ABCD) - (w(B) + w(C) + w(D))\n\nFor calculation the hyphen widths, the exact conditions would be\nover-determined, even when the possibility for individual hyphen\nwidths (instead of simply the text width of a hyphen character) would\nbe used. However, a simple approach of fixed hyphen widths will have\nnear-perfect results, so this is kept simple.\n\n\nAutomatic Hyphenation\n=====================\n\nWhen soft hyphens are used, words are immediately divided into\ndifferent parts, and so different instances of\ndw::Textblock::Word. Automatic hyphenation (using Liang's algorithm)\nis, however, not applied always, but only when possibly needed, after\ncalculating a line without hyphenation:\n\n- When the line is tight, the last word of the line is hyphenated;\n  possibly this will result in a line with less parts of this word,\n  and so a less tight line.\n- When the line is loose, and there is another word (for the next\n  line) available, this word is hyphenated; possibly, some parts of\n  this word are taken into this line, making it less loose.\n\nAfter this, the line is re-calculated.\n\nA problem arrises when the textblock is rewrapped, e.&nbsp;g. when the\nuser changes the window width. In this case, some new instances of\ndw::Textblock::Word must be inserted into the word list,\ndw::Textblock::words. This word list is implemented as an array, which\nis dynamically increased; a simple approach would involve moving all\nof the <i>n</i> elements after position <i>i</i>, so\n<i>n</i>&nbsp;-&nbsp;<i>i</i> steps are necessary. This would not be a\nproblem, since O(n) steps are necessary; however, this will be\nnecessary again for the next hyphenated word (at the end of a\nfollowing line), and so on, so that\n(<i>n</i>&nbsp;-&nbsp;<i>i</i><sub>1</sub>) +\n(<i>n</i>&nbsp;-&nbsp;<i>i</i><sub>2</sub>) + ..., with\n<i>i</i><sub>1</sub>&nbsp;&lt;&nbsp;<i>i</i><sub>2</sub>&nbsp;&lt;&nbsp;...,\nwhich results in O(n<sup>2</sup>) steps. For this reason, the word\nlist is managed by the class lout::misc::NotSoSimpleVector, which uses\na trick (a second array) to deal with exactly this problem. See there\nfor more details.\n\n\nTests\n=====\n\nThere are test HTML files in the <i>test</i> directory. Also, there is\na program testing automatic hyphenation, <i>test/liang</i>, which can\nbe easily extended.\n\n\nBugs and Things Needing Improvement\n===================================\n\nHigh Priority\n-------------\n\nNone.\n\nMedium Priority\n---------------\n\nNone.\n\nLow Priority\n------------\n\n**Mark the end of a paragraph:** Should dw::core::Content::BREAK still\nbe used? Currently, this is redundant to\ndw::Textblock::BadnessAndPenalty.\n\nSolved (Must Be Documented)\n---------------------------\n\nThese have been solved recently and should be documented above.\n\n*Bugs in hyphenation:* There seem to be problems when breaking words\ncontaining hyphens already. Example: \"Abtei-Stadt\", which is divided\ninto \"Abtei-\" and \"Stadt\", resulting possibly in\n&quot;Abtei-<span></span>-[new line]Stadt&quot;. See also below under\n\"Medium Priority\", on how to deal with hyphens and dashes.\n\n**Solution:** See next.\n\n*Break hyphens and dashes:* The following rules seem to be relevant:\n\n- In English, an em-dash is used with no spaces around. Breaking\n  before and after the dash should be possible, perhaps with a\n  penalty > 0. (In German, an en-dash (Halbgeviert) with spaces around\n  is used instead.)\n- After a hyphen, which is part of a compound word, a break should be\n  possible. As described above (\"Abtei-Stadt\"), this collides with\n  hyphenation.\n\nWhere to implement? In the same dynamic, lazy way like hyphenation? As\npart of hyphenation?\n\nNotice that Liang's algorithm may behave different regarding hyphens:\n\"Abtei-Stadt\" is (using the patterns from CTAN) divided into \"Abtei-\"\nand \"Stadt\", but \"Nordrhein-Westfalen\" is divided into \"Nord\",\n\"rhein-West\", \"fa\", \"len\": the part containing the hyphen\n(\"rhein-West\") is untouched. (Sorry for the German words; if you have\ngot English examples, send them me.)\n\n**Solution for both:** This has been implemented in\ndw::Textblock::addText, in a similar way to soft hyphens. Liang's\nalgorithm now only operates on the parts: \"Abtei\" and \"Stadt\";\n\"Nordrhein\" and \"Westfalen\".\n\n*Hyphens in adjacent lines:* It should be simple to assign a larger\npenalty for hyphens, when the line before is already hyphenated. This\nway, hyphens in adjacent lines are penalized further.\n\n**Solved:** There are always two penalties. Must be documented in\ndetail.\n\n*Incorrect calculation of extremes:* The minimal width of a text block\n(as part of the width extremes, which are mainly used for tables) is\ndefined by everything between two possible breaks. A possible break\nmay also be a hyphenation point; however, hyphenation points are\ncalculated in a lazy way, when the lines are broken, and not when\nextremes are calculated. So, it is a matter of chance whether the\ncalculation of the minimal width will take the two parts \"dil-\" and\n\"lo\" into account (when \"dillo\" has already been hyphenated), or only\none part, \"dillo\" (when \"dillo\" has not yet been hyphenated),\nresulting possibly in a different value for the minimal width.\n\nPossible strategies to deal with this problem:\n\n- Ignore. The implications should be minimal.\n- Any solution will make it neccessary to hyphenate at least some\n  words when calculating extremes. Since the minimal widths of all\n  words are used to calculate the minimal width of the text block, the\n  simplest approach will hyphenate all words. This would, of course,\n  eliminate the performance gains of the current lazy approach.\n- The latter approach could be optimized in some ways. Examples: (i)\n  If a word is already narrower than the current accumulated value for\n  the minimal width, it makes no sense to hyphenate it. (ii) In other\n  cases, heuristics may be used to estimate the number of syllables,\n  the width of the widest of them etc.\n\n**Solved:** Hyphenated parts of a word are not considered anymore for\nwidth extremes, but only whole words. This is also one reason for the\nintroduction of the paragraphs list.\n\n**Also:**\n\n- Configuration of penalties.\n\n*/\n"
  },
  {
    "path": "devdoc/dw-map.doc",
    "content": "/** \\page dw-map Dillo Widget Documentation Map\n\nThis maps includes special documentations as well as longer comments\nin the sources. Arrows denote references between the documents.\n\n\\dot\ndigraph G {\n   rankdir=LR;\n   node [shape=record, fontname=Helvetica, fontsize=8];\n   fontname=Helvetica; fontsize=8;\n\n    dw_overview [label=\"Dillo Widget Overview\", URL=\"\\ref dw-overview\"];\n    dw_usage [label=\"Dillo Widget Usage\", URL=\"\\ref dw-usage\"];\n    dw_layout_views [label=\"Layout and Views\", URL=\"\\ref dw-layout-views\"];\n    dw_layout_widgets [label=\"Layout and Widgets\",\n                       URL=\"\\ref dw-layout-widgets\"];\n    dw_widget_sizes [label=\"Sizes of Dillo Widgets\",\n                     URL=\"\\ref dw-widget-sizes\"];\n    dw_changes [label=\"Changes to the GTK+-based Release Version\",\n                URL=\"\\ref dw-changes\"];\n    dw_images_and_backgrounds [label=\"Images and Backgrounds in Dw\",\n                               URL=\"\\ref dw-images-and-backgrounds\"];\n    dw_Image [label=\"dw::Image\", URL=\"\\ref dw::Image\"];\n    dw_core_Imgbuf [label=\"dw::core::Imgbuf\", URL=\"\\ref dw::core::Imgbuf\"];\n    dw_core_SelectionState [label=\"dw::core::SelectionState\",\n                              URL=\"\\ref dw::core::SelectionState\"];\n    dw_core_style [label=\"dw::core::style\", URL=\"\\ref dw::core::style\"];\n    dw_Table [label=\"dw::Table\", URL=\"\\ref dw::Table\"];\n    dw_Textblock [label=\"dw::Textblock\", URL=\"\\ref dw::Textblock\"];\n    dw_core_ui [label=\"dw::core::ui\", URL=\"\\ref dw::core::ui\"];\n\n    dw_overview -> dw_changes;\n    dw_overview -> dw_usage;\n    dw_overview -> dw_core_style;\n    dw_overview -> dw_core_ui;\n    dw_overview -> dw_images_and_backgrounds;\n    dw_overview -> dw_layout_widgets;\n    dw_overview -> dw_widget_sizes;\n    dw_overview -> dw_layout_views;\n\n    dw_usage -> dw_Table;\n    dw_usage -> dw_Textblock;\n    dw_usage -> dw_core_style;\n    dw_usage -> dw_core_ui;\n    dw_usage -> dw_images_and_backgrounds;\n\n    dw_layout_widgets -> dw_widget_sizes;\n    dw_layout_widgets -> dw_core_SelectionState;\n\n    dw_widget_sizes -> dw_Table;\n    dw_widget_sizes -> dw_Textblock;\n\n    dw_images_and_backgrounds -> dw_core_Imgbuf;\n    dw_images_and_backgrounds -> dw_Image;\n\n    dw_core_style -> dw_Textblock;\n}\n\\enddot\n*/"
  },
  {
    "path": "devdoc/dw-miscellaneous.doc",
    "content": "/** \\page dw-miscellaneous Miscellaneous Notes on Dw\n\nThis is a barely sorted list of issues which I consider noteworthy,\nbut have yet to be moved to other parts of the documentation (which is\npartly to be created).\n\nGeneral\n=======\n\nWidget allocation outside of parent allocation\n----------------------------------------------\nA widget allocation outside of the allocation of the parent is\nallowed, but the part outside is not visible.\n\nWhich widgets may be drawn?\n---------------------------\nAll drawing starts with the toplevel widget\n(cf. dw::core::Widget::queueDrawArea, dw::core::Layout::queueDraw, and\ndw::core::Layout::expose), and a widget has to draw its children, in a\nway consistent with their stacking order.\n\nThere are two exceptions:\n\n1. Direct descendants, which are not children, may be drawn, if the\n   parent can distinguish them and so omit drawing them a second\n   time. See dw::core::StackingContextMgr and \\ref dw-stacking-context.\n   Parents should not draw children in flow for which\n   dw::core::StackingContextMgr::handledByStackingContextMgr returns\n   true.\n2. Interrupted drawing: via dw::core::Widget::drawInterruption; see\n   \\ref dw-interrupted-drawing.\n\nSimilar rules apply to handling mouse events\n(dw::core::Widget::getWidgetAtPoint).\n\nInterrupted drawing\n-------------------\n→ \\ref dw-interrupted-drawing.\n\nSimilar rules apply to handling mouse events\n(dw::core::Widget::getWidgetAtPoint).\n\nExtra space\n-----------\nShould dw::core::Widget::calcExtraSpace be called from\ndw::core::Widget::getExtremes?\n\n\nWidgets out of flow\n===================\n\ndw::Textblock::getGeneratorWidth\n--------------------------------\nRe-evaluate dw::Textblock::getGeneratorWidth (especially the limitation on\ninstances of dw::Textblock) for positioned elements. Is this method really only\ncalled for floats?\n\n\nWidget sizes\n============\n\nRelation between dw::core::Widget::markSizeChange and  dw::core::Widget::queueResize\n------------------------------------------------------------------------------------\nThe following comment should be re-evaluated. Implementing incremental resizing\nfor dw::oof::OOFFloatsMgr seems to fix the performance problems, but this should\nbe examined further.\n\n<div style=\"text-decoration: line-through; color: #606060\">\ndw::oof::OOFFloatsMgr::markSizeChange (called from\ndw::Textblock::markSizeChange) calls dw::oof::OOFAwareWidget::updateReference,\nwhose implementation dw::Textblock::updateReference calls\ndw::core::Widget::queueResize. This may result in a recursion,\n\n- for which it is not clear whether it ends in all cases (although endless cases\n  are not known yet), and\n- which nevertheless may take much time in cases where the number of calls\n  increases exponentially with the depth of the widget tree.\n\nThe recent change in dw::Textblock::updateReference (`if (lines->size () > 0)`)\nseems to fix the performance problem, but the issue should be examined further,\nalbeit with lower priority. Especially, it has to be determined, under which\nconditions it is allowed to (directly or indirectly) call\ndw::core::Widget::queueResize within an implementation of\ndw::core::Widget::markSizeChange.\n\nHere is the orginal test case (slow, when `if (lines->size () > 0)` is removed\nagain):\n\n    (for i in $(seq 1 20); do echo '<div style=\"float:left\"><div></div>'; done) > tmp.html; src/dillo tmp.html\n\nYou may change the numner (20), or examine smaller cases with\n<a href=\"http://home.gna.org/rtfl/\">RTFL</a>:\n\n    (for i in $(seq 1 3); do echo '<div style=\"float:left\"><div></div>'; done) > tmp.html; src/dillo tmp.html | rtfl-objview -OM -A \"*\" -a resize -a resize.oofm\n</div>\n\n*/\n"
  },
  {
    "path": "devdoc/dw-out-of-flow-floats.doc",
    "content": "/** \\page dw-out-of-flow-floats Handling Elements Out Of Flow: Floats\n\nTODO: Much missing.\n\n(Historical) Note: Floats make use of \\ref dw-size-request-pos, which\nreduces the complexity of a previous design.\n\n\nSorting floats\n==============\n\nFloats are sorted, to make binary search possible, in these lists:\n\n- for each generator: dw::OutOfFlowMgr::TBInfo::leftFloatsGB and\n  dw::OutOfFlowMgr::TBInfo::rightFloatsGB;\n- for the container: dw::OutOfFlowMgr::leftFloatsCB and\n  dw::OutOfFlowMgr::rightFloatsCB.\n\nThe other two lists, dw::OutOfFlowMgr::leftFloatsAll and\ndw::OutOfFlowMgr::rightFloatsAll are not sorted at all.\n\nNew floats are always added to the end of either list; this order is\ncalled *generation order*. See also above: *GB lists and CB lists*.\n\nOn the other hand, there are different sorting criteria, implemented\nby different comparators, so that different kinds of keys may be used\nfor searching. These sorting criteria are equivalent to the generation\norder.\n\ndw::OutOfFlowMgr::Float::CompareSideSpanningIndex compares\n*sideSpanningIndex* (used to compare floats to those on the respective\nother side); if you look at the definition\n(dw::OutOfFlowMgr::addWidgetOOF) it becomes clear that this order is\nequivalent to the generation order.\n\ndw::OutOfFlowMgr::Float::CompareGBAndExtIndex compares *externalIndex*\nfor floats with same generators, otherwise: (i) if one generator (T1)\nis a direct ancestor of the other generator (T2), the child of T1,\nwhich is an ancestor of, or identical to, T2 is compared to the float\ngenerated by T1, using *externalIndex*, as in this example:\n\n    T1 -+-> child --> ... -> T2 -> Float\n        `-> Float\n\nOtherwise, the two blocks are compared, according to their position in\ndw::OutOfFlowMgr::tbInfos:\n\n    common ancestor -+-> ... --> T1 -> Float\n                      `-> ... --> T2 -> Float\n\nThis is equivalent to the generation order, as long it is ensured that\n*externalIndex* reflects the generation order within a generating\nblock, for both floats and child blocks.\n\ndw::OutOfFlowMgr::Float::ComparePosition ...\n\n\nMiscellaneous notes\n===================\n\nHandling collisions\n-------------------\nThe CSS specification allows two strategies to deal with colliding\nfloats: placing the second float beside or below the first one. Many\nother browsers implement the first approach, while dillo implements\nthe second one, which may cause problems when the author assumes the\nfirst. Example: the \"tabs\" at the top of every page at Wikipedia\n(\"Article\", \"Talk\", ...).\n\nFloat containers in flow\n------------------------\nConsider the following HTML snippet:\n\n    <body>\n      <img src=\"....jpg\" style=\"float:right\">\n      <p style=\"overflow:hidden\">Text</p>\n    </body>\n\nInterestingly, dillo shows \"Text\" always *below* the image, even if\nthere is enough space left of it. An investigation shows that the\nparagraph (&lt;p&gt;) is regarded as own floats container (because of\n*overflow:hidden*), so the floats container above (&lt;body&gt;)\nregards this block as widget which must be fit between the floats\n(dw::Textblock::mustBorderBeRegarded &gt;\ndw::Textblock::getWidgetRegardingBorderForLine). However, since a\ntextblock in flow always covers (at least) the whole available width,\nwhich is defined *without* considering floats, the space left of the\nfloat will always be to narrow, so that the paragraph is moved below\nthe float, by inserting an empty line before.\n\nWhen searching for a solution, several difficulties show up:\n\n1. The available width, which is used for the width of the textblock,\n   is defined independent of floats. Aside from problems when changing\n   this definition, a dependance on floats would be difficult to\n   implement, since *sizeRequest* is independent of a position. (See\n   also \\ref dw-out-of-flow.)\n2. I must admit that I do not rembember the exact rationale and the\n   test case behind adding the exception in\n   dw::Textblock::getWidgetRegardingBorderForLine (see above), but\n   simply removing this exception will result in a possible\n   overlapping of floats from both containers, since no collisions are\n   tested for.\n3. On the other hand, mixing the float containers (interaction of two\n   or more instances of dw::oof::OOFFloatsMgr), e.&nbsp;g. for\n   collision tests, would become too complex and possibly result in\n   performance problems.\n\nInstead, this approach is focussed:\n\n- Goal: the paragraph is narrowed so it fits, *as a whole*, between\n  the floats.\n- The approach is to remove the exception in\n  dw::Textblock::getWidgetRegardingBorderForLine. A textblock, which\n  is a float container in flow (as this paragraph), is returned by\n  this method and so dw::Textblock::mustBorderBeRegarded returns\n  *true*. This will put this paragraph again at the correct position.\n- To avoid overlappings, the linebreaking width of this paragraph\n  (which is also used for positioning of floats) is the available\n  width, minus the maximal float width on either side. (This is an\n  approach similar to the one dw::Ruler will use soon). Most likely,\n  some new methods will have to be added to calculate this.\n- For paragraphs like this, dw::Textblock::borderChanged must rewrap\n  all lines; *y* is irrelevant in this case.\n- Since the textblock will tend to become taller when getting\n  narrower, and so possibly cover more (wider) floats, and so become\n  narrower again etc., there may be multible solutions for calculating\n  the size. Generally, a smaller height (and so larger width) is\n  preferred.\n- There remains a problem: what if a word is too large? Should a\n  textblock of this kind then reard the floats in detail, to insert\n  empty lines when needed?\n\n<b>Real-world cases:</b> *Overflow:hidden* is set for headings in\nWikipedia, and so this case occurs when there is a float (thumb image)\nbefore a heading. See e.&nbsp;g.\n<a href=\"http://de.wikipedia.org/wiki/Emmerich_am_Rhein#Ans.C3.A4ssige_Unternehmen\">this page</a>\nand scroll a bit up; the company logos should be right of this section.\n\n<b>Priority:</b> Since this is not a regression, compared to not\nsupporting floats at all, a fix is not urgent for a new release.\n\nResizing\n--------\nHas the case that a float changes its position to be regarded? Probably yes, but\ncases where no other mechanisms come into play are rather unlikely.\n\n<b>Priority:</b> If this plays a role, this means a regression compared to not\nsupporting floats at all.\n\n*/\n"
  },
  {
    "path": "devdoc/dw-out-of-flow-positioned.doc",
    "content": "/** \\page dw-out-of-flow-positioned Handling Elements Out Of Flow: Positioned Elements\n\n<div style=\"border: 2px solid #ffff00; margin: 1em 0; padding: 0.5em\n  1em; background-color: #ffffe0\">Positioned elements have been\n  deactivated, during the development of \\ref dw-size-request-pos, to\n  focus on floats. See dw::core::IMPL_POS in file dw/core.hh.</div>\n\n\nMiscellaneous notes\n===================\n\nGeneral\n-------\n(See also *relative positions* below.)\n\nWhat about negative positions?\n\ndw::oof::OutOfFlowMgr::tellPosition1 and\ndw::oof::OutOfFlowMgr::tellPosition2 could be combined again, and\ncalled in the respective circumstance, depending on\ndw::oof::OutOfFlowMgr::mayAffectBordersAtAll.\n\nRelative positions\n------------------\n**General Overview:** At the original position, a space as large as\nthe positioned element is left. This is implemented by assigning a\nsize to the widget *reference*. For this there are two new methods:\ndw::oof::OutOfFlowMgr::calcWidgetRefSize and\ndw::oof::OOFAwareWidget::widgetRefSizeChanged.\n\n**Bug:** Since the size of a relatively positioned element should be\ncalculated as if it was in flow, the available width should be\ndelegated to the *generator*; however, since\ndw::oof::OOFPosRelMgr::dealingWithSizeOfChild returns *false* in all\ncases, it is delegated to the *container*. **Idea for fix:**\ndw::oof::OOFPosRelMgr::dealingWithSizeOfChild should return *false* if\nand only if the generator of the child is the container (to prevent an\nendless recursion). In other cases,\ndw::oof::OOFPosRelMgr::getAvailWidthOfChild and\ndw::oof::OOFPosRelMgr::getAvailHeightOfChild should directly call the\nrespective methods of the *generator*, which must be made public then.\n\n**Performance:** In many cases, the first call of\ndw::oof::OOFPosRelMgr::sizeAllocateEnd will queue again the resize\nidle, since some elements are not considered in\ndw::oof::OOFPosRelMgr::getSize. One case could be removed: if a\npositioned element has *left* = *top* = 0, and its total size (the\nrequisition) is equal to the space left at the original position, the\nsize of the widget *reference*\n(dw::oof::OOFAwareWidget::getRequisitionWithoutOOF, see\ndw::oof::OOFPosRelMgr::calcWidgetRefSize).\n\n**Documentation:** Describe why the latter is not covered by\ndw::oof::OOFPositionedMgr::doChildrenExceedContainer. (Is this really\nthe case?)\n\n**Open:** Stacking order? Furthermore: a relatively positioned element\ndoes not always constitute a containing block (see CSS specification).\n\nFixed positions\n---------------\nCurrently, fixedly positioned elements are positioned relative to the\ncanvas, not to the viewport. For a complete implementation, see \\ref\ndw-fixed-positions.\n\n*/\n"
  },
  {
    "path": "devdoc/dw-out-of-flow.doc",
    "content": "/** \\page dw-out-of-flow Handling Elements Out Of Flow\n\nIntroduction\n============\n\nThis texts deals with both floats and positioned elements, which have\nin common that there is a distinction between generating block and\ncontaining block (we are here using the same notation as in the\nCSS&nbsp;2 specification). Consider this snippet (regarding floats):\n\n\n    <ul>\n      <li>Some text.</li>\n      <li>\n        <div style=\"float:right; width: 50%\">Some longer text, so\n          that the effect described in this passage can be\n          demonstrated.\n        </div>\n        Some more and longer text.</li>\n      <li>Final text. Plus some more to demonstrate how text flows\n        around the float on the right side.</li>\n    </ul>\n\nwhich may be rendered like this\n\n\\image html dw-floats-01.png\n\nThe float (the DIV section, yellow in the image) is defined\n(\"generated\") within the list item (green), so, in CSS 2 terms, the\nlist item is the generating block of the float. However, as the image\nshows, the float is not contained by the list item, but another block,\nseveral levels above (not shown here). In terms of ::dw, this means\nthat the dw::Textblock representing the float cannot be a child of the\ndw::Textblock representing the generating block, the list item, since\nthe allocation of a child widget must be within the allocation of the\nparent widget. Instead, to each dw::Textblock, another dw::Textblock\nis assigned as the containing box.\n\n(Notice also that other text blocks must regard floats to calculate\ntheir borders, and so their size. In this example, the following list\nitem (gray) must consider the position of the float. This is discussed\nin detail in the following section, _Implementation overview_.)\n\n\nImplementation overview\n=======================\n\n- dw::oof::OOFAwareWidget is the base for both generators and containers.\n  dw::Textblock and dw::Table are based on this, but dw::Table is only relevant\n  for positioned elements, so much simpler than dw::Textblock.\n- For a given textblock (or, generally, generator), the float container is not\n  necessary the same container as for positioned elements. For this reason,\n  dw::oof::OOFAwareWidget::oofContainer is an array.\n- The containers are set in dw::oof::OOFAwareWidget::notifySetAsTopLevel and\n  dw::oof::OOFAwareWidget::notifySetParent.\n- If a widget is out of flow, the generating widget keeps a reference with the\n  type dw::core::Content::WIDGET_OOF_REF, while the containing block refers to\n  it as dw::core::Content::WIDGET_OOF_CONT.  For widgets within flow,\n  dw::core::Content::WIDGET_IN_FLOW is used. Notice that in the first case,\n  there are two pieces of content referring to the same widget. An application\n  of this distinction is iterators. (For selection and searching, the generating\n  hierarchy is used, whih is different from the widget hierarchy.)\n\nHandling widgets out of flow is partly the task of class implementing\ndw::oof::OutOfFlowMgr, which is stored by dw::oof::OOFAwareWidget::outOfFlowMgr,\nbut only for containing blocks. Generating blocks should refer\nto *container->outOfFlowMgr[...]*.\n\nOverview of the dw::oof::OutOfFlowMgr hierarchy;\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\"];\n   fontname=Helvetica; fontsize=8;\n\n   OutOfFlowMgr [URL=\"\\ref dw::oof::OutOfFlowMgr\"; color=\"#a0a0a0\"];\n   OOFFloatsMgr [URL=\"\\ref dw::oof::OOFFloatsMgr\"];\n   OOFPositionedMgr [URL=\"\\ref dw::oof::OOFPositionedMgr\"; color=\"#a0a0a0\"];\n   OOFPosAbsLikeMgr [URL=\"\\ref dw::oof::OOFPosAbsLikeMgr\"; color=\"#a0a0a0\"];\n   OOFPosAbsMgr [URL=\"\\ref dw::oof::OOFPosAbsMgr\"];\n   OOFPosFixedMgr [URL=\"\\ref dw::oof::OOFPosFixedMgr\"];\n   OOFPosRelMgr [URL=\"\\ref dw::oof::OOFPosRelMgr\"];\n\n   OutOfFlowMgr -> OOFFloatsMgr;\n   OutOfFlowMgr -> OOFPositionedMgr;\n   OOFPositionedMgr -> OOFPosAbsLikeMgr;\n   OOFPosAbsLikeMgr -> OOFPosAbsMgr;\n   OOFPosAbsLikeMgr -> OOFPosFixedMgr;  \n   OOFPositionedMgr -> OOFPosRelMgr;\n}\n\\enddot\n</div>\n\n\nFurther details\n===============\n\n- \\ref dw-out-of-flow-floats\n- \\ref dw-out-of-flow-positioned\n\n*/\n"
  },
  {
    "path": "devdoc/dw-overview.doc",
    "content": "/** \\page dw-overview Dillo Widget Overview\n\nNote: If you are already familiar with the Gtk+-based version of Dw,\nread \\ref dw-changes.\n\n\nThe module Dw (Dillo Widget) is responsible for the low-level rendering of\nall resources, e.g. images, plain text, and HTML pages (this is the\nmost complex type). Dw is \\em not responsible for parsing HTML, or\ndecoding image data. Furthermore, the document tree, which is planned\nfor CSS, is neither a part of Dw, instead, it is a new module on top\nof Dw.\n\nThe rendering, as done by Dw, is split into two phases:\n\n<ul>\n<li> the \\em layouting, this means calculating the exact positions of\n     words, lines, etc. (in pixel position), and\n<li> the \\em drawing, i.e. making the result of the layouting visible\n     on the screen.\n</ul>\n\nThe result of the layouting allocates an area, which is called\n\\em canvas.\n\n<h2>Structure</h2>\n\nThe whole Dw module can be split into the following parts:\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"open\", fontname=Helvetica, fontsize=10,\n         labelfontname=Helvetica, labelfontsize=10,\n         color=\"#404040\", labelfontcolor=\"#000080\"];\n\n   subgraph cluster_core {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n      label=\"Platform independent core\";\n\n      Layout [URL=\"\\ref dw::core::Layout\"];\n      Platform [URL=\"\\ref dw::core::Platform\", color=\"#ff8080\"];\n      View [URL=\"\\ref dw::core::View\", color=\"#ff8080\"];\n      Widget [URL=\"\\ref dw::core::Widget\", color=\"#a0a0a0\"];\n   }\n\n   subgraph cluster_fltk {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n      label=\"FLTK specific part (as an\\nexample for the platform specific\\n\\\nimplementations)\";\n\n      subgraph cluster_fltkcore {\n         style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n         label=\"FLTK core\";\n\n         FltkPlatform [URL=\"\\ref dw::fltk::FltkPlatform\"];\n         FltkView [URL=\"\\ref dw::fltk::FltkView\", color=\"#ff8080\"];\n      }\n\n      FltkViewport [URL=\"\\ref dw::fltk::FltkViewport\"];\n      FltkPreview [URL=\"\\ref dw::fltk::FltkPreview\"];\n   }\n\n   subgraph cluster_widgets {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n      label=\"Platform independent widgets\";\n\n      Textblock [URL=\"\\ref dw::Textblock\"];\n      AlignedTextblock [URL=\"\\ref dw::AlignedTextblock\", color=\"#a0a0a0\"];\n      Table [URL=\"\\ref dw::Table\"];\n      Image [URL=\"\\ref dw::Image\"];\n      etc1 [label=\"...\"];\n      etc2 [label=\"...\"];\n   }\n\n   Layout -> Platform [headlabel=\"1\", taillabel=\"1\"];\n   Layout -> View [headlabel=\"*\", taillabel=\"1\"];\n\n   Layout -> Widget [headlabel=\"1\", taillabel=\"1\", label=\"topLevel\"];\n   Widget -> Widget [headlabel=\"*\", taillabel=\"1\", label=\"children\"];\n\n   Widget -> Textblock [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\"];\n   Widget -> Table [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\"];\n   Widget -> Image [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\"];\n   Widget -> etc1 [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\"];\n   Textblock -> AlignedTextblock [arrowhead=\"none\", arrowtail=\"empty\",\n                                  dir=\"both\"];\n   AlignedTextblock -> etc2 [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\"];\n\n   Platform -> FltkPlatform [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n                             style=\"dashed\"];\n   FltkPlatform -> FltkView [headlabel=\"*\", taillabel=\"1\"];\n\n   View -> FltkView [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\"];\n   FltkView -> FltkViewport [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n                             style=\"dashed\"];\n   FltkView -> FltkPreview [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n                            style=\"dashed\"];\n}\n\\enddot\n\n<center>[\\ref uml-legend \"legend\"]</center>\n\n\\em Platform means in most cases the underlying UI toolkit\n(e.g. FLTK). A layout is bound to a specific platform, but multiple\nplatforms may be handled in one program.\n\nA short overview:\n\n<ul>\n<li> dw::core::Layout is the central class, it manages the widgets and the\n     view, and provides delegation methods for the platform.\n\n<li> The layouting is done by a tree of widgets (details are described in\n     \\ref dw-layout-widgets), also the drawing, which is finally delegated\n     to the view.\n\n<li> The view (implementation of dw::core::View) provides primitive methods\n     for drawing, but also have an influence on\n     the canvas size (via size hints). See \\ref dw-layout-views for details.\n\n<li> Some platform dependencies are handled by implementations\n     of dw::core::Platform.\n</ul>\n\n\n<h3>Header Files</h3>\n\nThe structures mentioned above can be found in the following header\nfiles:\n\n<ul>\n<li> Anything from the Dw core in core.hh. Do not include the single files.\n\n<li> The single widgets can be found in the respective header files, e.g.\n     image.hh for dw::Image.\n\n<li> The core of the FLTK implementation is defined in fltkcore.hh. This\n     includes dw::fltk::FltkPlatform, dw::fltk::FltkView, but not the concrete\n     view implementations.\n\n<li> The views can be found in single header files, e.g fltkviewport.hh for\n     dw::fltk::FltkViewport.\n</ul>\n\n\n<h2>Further Documentations</h2>\n\nA complete map can be found at \\ref dw-map.\n\n<ul>\n<li> For learning, how to use Dw, read \\ref dw-usage and related documents,\n     dw::core::style, dw::core::ui and \\ref dw-images-and-backgrounds.\n<li> Advanced topics are described in \\ref dw-layout-widgets,\n     \\ref dw-widget-sizes and \\ref dw-layout-views.\n</ul>\n\n*/\n"
  },
  {
    "path": "devdoc/dw-size-request-pos-01.html",
    "content": "<p style=\"margin-bottom: 5em\"><i>This is the source of the image\n<a href=\"dw-size-request-pos-01.png\">dw-size-request-pos-01.png</a>.</i></p>\n\n<table lang=\"en\">\n  <td style=\"width: 220px\">\n    <p>\n      <div style=\"float:right\">\n\t<img src=\"xhttps://www.gnu.org/graphics/heckert_gnu.small.png\">\n\t<p style=\"text-align: center\"><i>A GNU</i></p>\n      </div>\n      Short.\n    </p>\n    <p>A longer paragraph, into which a float extends, which has been\n      defined in the previous paragraph. </p>\n  </td>\n  <td style=\"vertical-align: middle\">\n    <br/>\n    <br/>\n    <br/>\n    <br/>\n    <br/>\n    <br/>\n    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<img src=\"http://upload.wikimedia.org/wikipedia/commons/8/80/CorrespMtl5.png\" style=\"width: 80px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>\n  <td style=\"width: 220px\">\n    <p>\n      <div style=\"float:right\">\n\t<img src=\"https://www.gnu.org/graphics/heckert_gnu.small.png\">\n\t<p style=\"text-align: center\"><i>A GNU</i></p>\n      </div>\n      Short.\n    </p>\n    <p>A longer paragraph, into which a float extends, which has been\n      defined in the previous paragraph. </p>\n  </td>\n</table>\n"
  },
  {
    "path": "devdoc/dw-size-request-pos.doc",
    "content": "/** \\page dw-size-request-pos Size requisitions depending on positions\n\n<div style=\"border: 2px solid #ffff00; margin: 1em 0;\n  padding: 0.5em 1em; background-color: #ffffe0\">The complex \"widget\n  sizes\" is currently divided into three documents: \\ref\n  dw-widget-sizes, \\ref dw-grows, and **Size requisitions depending on\n  positions** (this document). Furthermore, there are some notes in\n  \\ref dw-miscellaneous.</div>\n\n\nMotivation\n==========\n\nAs described in \\ref dw-out-of-flow (*The sizeRequest/sizeAllocate\nproblem*), the principle that the size of a widget depends only on the\nsizes of its children causes some problems with floats; the current\nsolution is a frequent correction by calling\ndw::core::Widget::queueResize. In this document, an alternative\napproach is presented.\n\n<div style=\"border: 2px solid #ffff00; margin: 1em 0; padding: 0.5em 1em;\n  background-color: #ffffe0\">This approach works very well for floats, but not\n  for positioned elements, which means that calling\n  dw::core::Widget::queueResize is still needed for the latter. On the other\n  hand, dw::oof::OOFFloatsMgr (which is much more complex than\n  dw::oof::OOFPositionedMgr) can be simplified quite much.</div>\n\n\nGeneral Idea\n============\n\nA widget size may depend on the position relative to an ancestor\nwidget. If a widget wants to get the size of a child widget, it should:\n\n1. call the new methods dw::core::Widget::numSizeRequestReferences and\n   dw::core::Widget::sizeRequestReference, which return all widgets\n   relative to which the child's position must be calculated;\n2. call dw::core::Widget::sizeRequest with the positions relative to\n   these widgets.\n\n<div style=\"border: 2px solid #ffff00; margin: 1em 0;\n  padding: 0.5em 1em; background-color: #ffffe0\">It is not sufficient\n  to work with *absolute* positions, since there may be an\n  interruption passing the positions so that absolute positions are\n  often not known.</div>\n\nAll positions passed to dw::core::Widget::sizeRequest must constitute\nthe position at which this child will be allocated.\n\nThere are situations where the parent widget is unable to determine\nthese positions before the size is known. An example: a textblock\nwidget cannot determine the positions of an inline widget (like an\nimage, or an inline block) before the line is finished; on the other\nhand, finishing the line depends on knowing the sizes of the inline\nwidgets.\n\nThis may result in a conflict, when the size of an inline widget\ndepends on positions. Generally, the only widget whose size depends on\npositions is dw::Textblock (the size will depend on the positions\nwithin its oof container, see \\ref dw-out-of-flow), so this conflict\noccurs with inline blocks.\n\nThis conflict is handled in different ways:\n\n1. Fortunately, this case is irrelevat for floats: an inline block\n   constitutes its own floats container, so that there is no dependance\n   on a position within another widget.\n\n2. For positioned elements, this case is relevant, since an inline\n   block is in most cases not a container for positioned elements. In\n   this case, a generator will call the methods\n   dw::oof::OutOfFlowMgr::tellIncompletePosition1 and\n   dw::oof::OutOfFlowMgr::tellIncompletePosition2, instead of\n   dw::oof::OutOfFlowMgr::tellPosition and\n   dw::oof::OutOfFlowMgr::tellPosition2, respectively. (Since this\n   case is irrelevant for floats,\n   dw::oof::OOFFloatsMgr::tellIncompletePosition1 and\n   dw::oof::OOFFloatsMgr::tellIncompletePosition2 are not implemented but\n   simply abort.)\n\n(This is not (yet) considered for borders: borders are only relevant\nfor floats, but conflicts do not occur for floats.)\n\n\nExtremes\n--------\nExtremes may depend on the position in an analogue way, see:\n\n- dw::core::Widget::numGetExtremesReferences,\n- dw::core::Widget::getExtremesReference, and\n- dw::core::Widget::getExtremes.\n\nResizing\n--------\nCurrently, the size of a widget has to be recalculated when:\n\n1. it has called dw::core::Widget::queueResize, or\n2. the size of a child widget has to be recalculated.\n\nSince for this new approach, the size does not only depend on the size of the\nchildren, the second condition must be modified. See beginning of\ndw::core::Widget::sizeRequest.\n\nAn implementation may have to consider, this too, especially when implementing\nincremental resizing (see \\ref dw-widget-sizes); see\ndw::Textblock::sizeRequestImpl as an example.\n\nRegarding changes of the position is not sufficient. Consider this example,\nwhere a float size changes as soon as the image is loaded:\n\n\\image html dw-size-request-pos-01.png\n\nThe second paragraph (\"A longer paragraph\" ...) stays at the same position, both\nabsolute and relative to the float container, but has to be rewrapped because of\nthe float. Instead, this is handled by dw::oof::OutOfFlowMgr::markSizeChange\n(and likewise dw::oof::OutOfFlowMgr::markExtremesChange), which is called by the\nimplementation of `markSizeChange` (or `markExtremesChange`, respectively) of\nthe OOF container. (See also the end of the comment of dw::oof::OOFAwareWidget.)\n\n\nPlan\n====\n\n1. General design (dw::core::Widget::sizeRequestReference, changes to\n   dw::core::Widget::sizeRequest). Completed.\n\n2. Implementation for dw::Textblock. Completed.\n\n3. Change interface of dw::oof::OutOfFlowMgr (this affects mostly only\n   comments). Completed.\n\n   Affects methods dw::oof::OutOfFlowMgr::tellPosition1,\n   dw::oof::OutOfFlowMgr::tellPosition2,\n   dw::oof::OutOfFlowMgr::getLeftBorder,\n   dw::oof::OutOfFlowMgr::getRightBorder,\n   dw::oof::OutOfFlowMgr::hasFloatLeft,\n   dw::oof::OutOfFlowMgr::hasFloatRight,\n   dw::oof::OutOfFlowMgr::getLeftFloatHeight, and\n   dw::oof::OutOfFlowMgr::getRightFloatHeight.\n\n4. Apply step 3 to calls within dw::Textblock. Completed.\n\n   <b>Attention:</b> After this step, and before completing the next steps, the\n   code is inconsistent and so incorrect.\n\n5. Implement step 3 for floats (affects dw::oof::OOFFloatsMgr). **MOSTLY\n   COMPLETED.**\n\n6. Implement step 3 for positioned elements (affects only\n   dw::oof::OOFPositionedMgr). **INCOMPLETE.** (But positioned elements are\n   currently deactivated.)\n\n\nIssues\n======\n\n- Since the signature of dw::core::Widget::sizeRequestImpl changes quite often\n  during the development of *size requisitions depending on positions*, a\n  simpler option dw::core::Widget::sizeRequestSimpl has been added. May be\n  removed again, after the design is stable.\n\n- As an alternative, passing the references may be done in a new method, which\n  is called *before* dw::core::Widget::sizeRequestImpl. This makes even more\n  sense, after dw::core::Widget::calcExtraSpace and\n  dw::core::Widget::calcExtraSpaceImpl have been extended by references.\n\n- There may be inconsistencies for widget styles; see\n  [revision f797436687fe](http://flpsed.org/hgweb/dillo_grows/rev/f797436687fe)\n  as an example for a fix. Perhaps a different approach, where breaks are added,\n  _if `display` has the value `block`_ (or analogue), will work better.\n\n*/\n"
  },
  {
    "path": "devdoc/dw-stacking-context.doc",
    "content": "/** \\page dw-stacking-context Handling stacking contexts\n\nStacking Context and dw::core::StackingContextMgr\n=================================================\n\nFor the definition of stacking contexts, see CSS 2.1 specification,\n\n- <a href=\"http://www.w3.org/TR/CSS2/visuren.html#z-index\">section\n     9.9.1: Specifying the stack level: the 'z-index' property</a> and\n- <a href=\"http://www.w3.org/TR/CSS2/zindex.html\">appendix E</a>.\n\nA widget establishes a stacking context when it is positioned and its\nstyle value of *z-index* is different from *auto* (see\ndw::core::StackingContextMgr::isEstablishingStackingContext). In this\ncase, it is assigned an instance of dw::core::StackingContextMgr,\nwhich has also access to the widgets establishing the child contexts.\n\n\nStacking Order\n==============\n\nThe stacking order influences\n\n1. the order in which child widgets are drawn (dw::core::Widget::draw),\n   and\n2. the order in which mouse events are dispatched to child widgets\n   (dw::core::Widget::getWidgetAtPoint).\n\nThe first is done from bottom to top, the latter from top to bottom.\n\nI'm here referring to the simplified description in\n<a href=\"http://www.w3.org/TR/CSS2/visuren.html#z-index\">section\n9.9.1</a>. The table shows a recommended order for the implementations\nof dw::core::Widget::draw and dw::core::Widget::getWidgetAtPoint\n(for the latter, read from bottom to top):\n \n<table>\n<tr>\n<th> CSS specification <th> Drawing <th> Mouse events\n<tr>\n<td> *1. the background and borders of the element forming the\n         stacking context.*\n<td> dw::core::Widget::drawBox\n<td> Nothing necessary.\n<tr>\n<td> *2. the child stacking contexts with negative stack levels (most\n         negative first).*\n<td> dw::core::StackingContextMgr::drawBottom (when defined)\n<td> dw::core::StackingContextMgr::getBottomWidgetAtPoint (when defined)\n<tr>\n<td> *3. the in-flow, non-inline-level, non-positioned descendants.*\n\n<td rowspan=\"4\"> When (i) widget specific content is drawn, then (ii)\n                 dw::oof::OOFAwareWidget::drawOOF is called, this will\n                 have this effect:\n\n                 1. all in-flow elements are drawn,\n                 2. floats are drawn and\n                 3. positioned elements with *z-index: auto* are drawn\n                    (latter two done by\n                    dw::oof::OOFAwareWidget::drawOOF, in this order).\n\n                 This order differs from the specified order, but\n                 since floats and in-flow elements do not overlap,\n                 this difference has no effect.\n\n                 Drawing in-line elements, floats and positioned\n                 elements with *z-index: auto* and should avoid\n                 duplicate calls: Widgets drawn by\n                 dw::core::StackingContextMgr::drawBottom and by\n                 dw::core::StackingContextMgr::drawTop should be\n                 excluded here. This can be tested with\n                 dw::core::StackingContextMgr::handledByStackingContextMgr.\n\n<td rowspan=\"4\"> Likewise, the implementation should (i) test\n                 dw::oof::OOFAwareWidget::getWidgetOOFAtPoint, and\n                 (ii) search through the chilren. Also, duplicate\n                 calls should be avoided using\n                 dw::core::StackingContextMgr::handledByStackingContextMgr.\n\n                 There are already the implementations\n                 dw::core::Widget::getWidgetAtPoint (ignoring\n                 dw::oof::OutOfFlowMgr) and\n                 dw::oof::OOFAwareWidget::getWidgetAtPoint (including\n                 dw::oof::OutOfFlowMgr).\n\n<tr>\n<td> *4. the non-positioned floats.*\n<tr>\n<td> *5. the in-flow, inline-level, non-positioned descendants,\n         including inline tables and inline blocks.*\n<tr>\n<td> (What about positioned elements with *z-index: auto*? Seems to be\n     missing in\n     <a href=\"http://www.w3.org/TR/CSS2/visuren.html#z-index\">section\n       9.9.1</a>, but mentioned in\n     <a href=\"http://www.w3.org/TR/CSS2/zindex.html\">appendix E</a>,\n     item 8.\n<tr>\n<td> *6. the child stacking contexts with stack level 0 and the\n         positioned descendants with stack level 0.*\n<td rowspan=\"2\"> dw::core::StackingContextMgr::drawTop (when defined)\n<td rowspan=\"2\"> dw::core::StackingContextMgr::getTopWidgetAtPoint\n                 (when defined)\n<tr>\n<td> *7. the child stacking contexts with positive stack levels (least\n         positive first).*\n</table>\n\nNote: This is not quite in conformance with the specification: this\ndescription refers to any widget, not only widgets establishing a\nstacking context. Does this make a difference?\n\n*/"
  },
  {
    "path": "devdoc/dw-usage.doc",
    "content": "/** \\page dw-usage Dillo Widget Usage\n\nThis document describes the usage of Dw, without going too much into\ndetail.\n\n\n<h2>Getting Started</h2>\n\nIn this section, a small runnable example is described, based on the\nFLTK implementation.\n\nAs described in \\ref dw-overview, the following objects are needed:\n\n<ul>\n<li> dw::core::Layout,\n<li> an implementation of dw::core::Platform (we will use\n     dw::fltk::FltkPlatform),\n<li> at least one implementation of dw::core::View (dw::fltk::FltkViewport),\n     and\n<li> some widgets (for this example, only a simple dw::Textblock).\n</ul>\n\nFirst of all, the necessary \\#include's:\n\n\\code\n#include <FL/Fl_Window.H>\n#include <FL/Fl.H>\n\n#include \"dw/core.hh\"\n#include \"dw/fltkcore.hh\"\n#include \"dw/fltkviewport.hh\"\n#include \"dw/textblock.hh\"\n\\endcode\n\nEverything is put into one function:\n\n\\code\nint main(int argc, char **argv)\n{\n\\endcode\n\nAs the first object, the platform is instantiated:\n\n\\code\n   dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform ();\n\\endcode\n\nThen, the layout is created, with the platform attached:\n\n\\code\n   dw::core::Layout *layout = new dw::core::Layout (platform);\n\\endcode\n\nFor the view, we first need a FLTK window:\n\n\\code\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Example\");\n   window->begin();\n\\endcode\n\nAfter this, we can create a viewport, and attach it to the layout:\n\n\\code\n   dw::fltk::FltkViewport *viewport =\n      new dw::fltk::FltkViewport (0, 0, 200, 300);\n   layout->attachView (viewport);\n\\endcode\n\nEach widget needs a style (dw::core::style::Style, see dw::core::style),\nso we construct it here. For this, we need to fill a\ndw::core::style::StyleAttrs structure with values, and call\ndw::core::style::Style::create (latter is done further below):\n\n\\code\n   dw::core::style::StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\\endcode\n\ndw::core::style::StyleAttrs::initValues sets several default\nvalues. The last line sets a margin of 5 pixels. Next, we need a\nfont. Fonts are created in a similar way, first, the attributes are\ndefined:\n\n\\code\n   dw::core::style::FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = dw::core::style::FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = dw::core::style::FONT_VARIANT_NORMAL;\n\\endcode\n\nNow, the font can be created:\n\n\\code\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\\endcode\n\nAs the last attributes, the background and forground colors are\ndefined, here dw::core::style::Color::createSimple must be called:\n\n\\code\n   styleAttrs.color =\n      dw::core::style::Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor =\n      dw::core::style::Color::create (layout, 0xffffff);\n\\endcode\n\nFinally, the style for the widget is created:\n\n\\code\n   dw::core::style::Style *widgetStyle =\n      dw::core::style::Style::create (layout, &styleAttrs);\n\\endcode\n\nNow, we create a widget, assign a style to it, and set it as the\ntoplevel widget of the layout:\n\n\\code\n   dw::Textblock *textblock = new dw::Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\\endcode\n\nThe style is not needed anymore (a reference is added in\ndw::core::Widget::setStyle), so it should be unreferred:\n\n\\code\n   widgetStyle->unref();\n\\endcode\n\nNow, some text should be added to the textblock. For this, we first\nneed another style. \\em styleAttrs can still be used for this. We set\nthe margin to 0, and the background color to \"transparent\":\n\n\\code\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n\n   dw::core::style::Style *wordStyle =\n      dw::core::style::Style::create (layout, &styleAttrs);\n\\endcode\n\nThis loop adds some paragraphs:\n\n\\code\n   for(int i = 1; i <= 10; i++) {\n      char buf[4];\n      sprintf(buf, \"%d.\", i);\n\n      char *words[] = { \"This\", \"is\", \"the\", buf, \"paragraph.\",\n                        \"Here\", \"comes\", \"some\", \"more\", \"text\",\n                        \"to\", \"demonstrate\", \"word\", \"wrapping.\",\n                        NULL };\n\n      for(int j = 0; words[j]; j++) {\n         textblock->addText(strdup(words[j]), wordStyle);\n\\endcode\n\nNotice the \\em strdup, dw::Textblock::addText will feel responsible\nfor the string, and free the text at the end. (This has been done to\navoid some overhead in the HTML parser.)\n\nThe rest is simple, it also includes spaces (which also have styles):\n\n\\code\n         textblock->addSpace(wordStyle);\n      }\n\\endcode\n\nFinally, a paragraph break is added, which is 10 pixels high:\n\n\\code\n      textblock->addParbreak(10, wordStyle);\n   }\n\\endcode\n\nAgain, this style should be unreferred:\n\n\\code\n   wordStyle->unref();\n\\endcode\n\nAfter adding text, this method should always be called (for faster\nadding large text blocks):\n\n\\code\n   textblock->flush ();\n\\endcode\n\nSome FLTK stuff to finally show the window:\n\n\\code\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\\endcode\n\nFor cleaning up, it is sufficient to destroy the layout:\n\n\\code\n   delete layout;\n\\endcode\n\nAnd the rest\n\n\\code\n   return errorCode;\n}\n\\endcode\n\nIf you compile and start the program, you should see the following:\n\n\\image html dw-example-screenshot.png\n\nTry to scroll, or to resize the window, you will see, that everything\nis done automatically.\n\nOf course, creating new widgets, adding text to widgets etc. can also\nbe done while the program is running, i.e. after fltk::run has been\ncalled, within timeouts, idles, I/O functions etc. Notice that Dw is\nnot thread safe, so that everything should be done within one thread.\n\nWith the exception, that you have to call dw::Textblock::flush,\neverything gets immediately visible, within reasonable times; Dw has\nbeen optimized for frequent updates.\n\n\n<h2>List of all Widgets</h2>\n\nThese widgets are used within dillo:\n\n<ul>\n<li>dw::core::ui::Embed\n<li>dw::AlignedTextblock\n<li>dw::Bullet\n<li>dw::Ruler\n<li>dw::Image\n<li>dw::ListItem\n<li>dw::Table\n<li>dw::TableCell\n<li>dw::Textblock\n</ul>\n\nIf you want to create a new widget, refer to \\ref dw-layout-widgets.\n\n\n<h2>List of Views</h2>\n\nThere are three dw::core::View implementations for FLTK:\n\n<ul>\n<li> dw::fltk::FltkViewport implements a viewport, which is used in the\n     example above.\n\n<li> dw::fltk::FltkPreview implements a preview window, together with\n     dw::fltk::FltkPreviewButton, it is possible to have a scaled down\n     overview of the whole canvas.\n\n<li> dw::fltk::FltkFlatView is a \"flat\" view, i.e. it does not support\n     scrolling. It is used for HTML buttons, see\n     dw::fltk::ui::FltkComplexButtonResource and especially\n     dw::fltk::ui::FltkComplexButtonResource::createNewWidget for details.\n</ul>\n\nMore informations about views in general can be found in \\ref\ndw-layout-views.\n\n\n<h2>Iterators</h2>\n\nFor examining generally the contents of widgets, there are iterators\n(dw::core::Iterator), created by the method\ndw::core::Widget::iterator (see there for more details).\n\nThese simple iterators only iterate through one widget, and return\nchild widgets as dw::core::Content::WIDGET. The next call of\ndw::core::Iterator::next will return the piece of contents \\em after\n(not within) this child widget.\n\nIf you want to iterate through the whole widget trees, there are two\npossibilities:\n\n<ol>\n<li> Use a recursive function. Of course, with this approach, you are\n     limited by the program flow.\n\n<li> Maintain a stack of iterators, so you can freely pass this stack\n     around. This is already implemented, as dw::core::DeepIterator.\n</ol>\n\nAs an example, dw::core::SelectionState represents the selected region\nas two instances of dw::core::DeepIterator.\n\n\n<h2>Finding Text</h2>\n\nSee dw::core::Layout::findtextState and dw::core::FindtextState\n(details in the latter). There are delegation methods:\n\n<ul>\n<li> dw::core::Layout::search and\n<li> dw::core::Layout::resetSearch.\n</ul>\n\n\n<h2>Anchors and Scrolling</h2>\n\nIn some cases, it is necessary to scroll to a given position, or to\nan anchor, programmatically.\n\n<h3>Anchors</h3>\n\nAnchors are defined by widgets, e.g. dw::Textblock defines them, when\ndw::Textblock::addAnchor is called. To jump to a specific anchor\nwithin the current widget tree, use dw::core::Layout::setAnchor.\n\nThis can be done immediately after assignig a toplevel widget, even\nwhen the anchor has not yet been defined. The layout will remember the\nanchor, and jump to the respective position, as soon as possible. Even\nif the anchor position changes (e.g., when an anchor is moved\ndownwards, since some space is needed for an image in the text above),\nthe position is corrected.\n\nAs soon as the user scrolls the viewport, this correction is not done\nanymore. If in dillo, the user request a page with an anchor, which is\nquite at the bottom of the page, he may be get interested in the text\nat the beginning of the page, and so scrolling down. If then, after\nthe anchor has been read and added to the dw::Textblock, this anchor\nwould be jumped at, the user would become confused.\n\nThe anchor is dismissed, too, when the toplevel widget is removed\nagain.\n\n\\todo Currently, anchors only define vertical positions.\n\n<h3>Scrolling</h3>\n\nTo scroll to a given position, use the method\ndw::core::Layout::scrollTo. It expects several parameters:\n\n<ul>\n<li>a horizontal adjustment parameter, defined by dw::core::HPosition,\n<li>a vertical adjustment parameter, defined by dw::core::VPosition, and\n<li>a rectangle (\\em x, \\em y, \\em width and \\em heigh) of the region\n    to be adjusted.\n</ul>\n\nIf you just want to move the canvas coordinate (\\em x, \\em y) into the\nupper left corner of the viewport, you can call:\n\n\\code\ndw::core::Layout *layout;\n// ...\nlayout->scrollTo(dw::core::HPOS_LEFT, dw::core::VPOS_TOP, 0, 0, 0, 0);\n\\endcode\n\nBy using dw::core::HPOS_NO_CHANGE or dw::core::VPOS_NO_CHANGE, you can\nchange only one dimension. dw::core::HPOS_INTO_VIEW and\ndw::core::VPOS_INTO_VIEW will cause the viewport to move as much as\nnecessary, that the region is visible in the viewport (this is\ne.g. used for finding text).\n\n\n<h2>Further Documentations</h2>\n\n<ul>\n<li> dw::core::style\n<li> dw::core::ui\n<li> \\ref dw-images-and-backgrounds\n</ul>\n\n*/\n"
  },
  {
    "path": "devdoc/dw-widget-sizes.doc",
    "content": "/** \\page dw-widget-sizes Sizes of Dillo Widgets\n\n<div style=\"border: 2px solid #ffff00; margin: 1em 0;\n  padding: 0.5em 1em; background-color: #ffffe0\">The complex \"widget\n  sizes\" is currently divided into three documents: **Sizes of Dillo\n  Widgets** (this document), \\ref dw-grows, and \\ref\n  dw-size-request-pos. Furthermore, there are some notes in\n  \\ref dw-miscellaneous.</div>\n\n<div style=\"border: 2px solid #ff4040; margin: 1em 0;\n  padding: 0.5em 1em; background-color: #fff0f0\"><b>Info:</b>\n  Not up to date, see other documents.</div>\n\nAllocation\n==========\n\nEach widget has an \\em allocation at a given time, this includes\n\n- the position (\\em x, \\em y) relative to the upper left corner of the\n  canvas, and\n- the size (\\em width, \\em ascent, \\em descent).\n\nThe \\em canvas is the whole area available for the widgets, in most\ncases, only a part is seen in a viewport. The allocation of the\ntoplevel widget is exactly the allocation of the canvas, i.e.\n\n- the position of the toplevel widget is always (0, 0), and\n- the canvas size is defined by the size of the toplevel widget.\n\nThe size of a widget is not simply defined by the width and the\nheight, instead, widgets may have a base line, and so are vertically\ndivided into an ascender (which height is called \\em ascent), and a\ndescender (which height is called \\em descent). The total height is so\nthe sum of \\em ascent and \\em descent.\n\nSizes of zero are allowed. The upper limit for the size of a widget is\ndefined by the limits of the C++ type \\em int.\n\n\\image html dw-size-of-widget.png Allocation of a Widget\n\nIn the example in the image, the widget has the following allocation:\n\n- \\em x = 50\n- \\em y = 50\n- \\em width = 150\n- \\em ascent = 150\n- \\em descent = 100\n\nThe current allocation of a widget is hold in\ndw::core::Widget::allocation. It can be set from outside by\ncalling dw::core::Widget::sizeAllocate. This is a concrete method,\nwhich will call dw::core::Widget::sizeAllocateImpl (see code of\ndw::core::Widget::sizeAllocate for details).\n\nFor trivial widgets (like dw::Bullet),\ndw::core::Widget::sizeAllocateImpl does not need to be\nimplemented. For more complex widgets, the implementation should call\ndw::core::Widget::sizeAllocate (not\ndw::core::Widget::sizeAllocateImpl) on all child widgets, with\nappropriate child allocations. dw::core::Widget::allocation should not\nbe changed here, this is already done in\ndw::core::Widget::sizeAllocate.\n\n\nRequisitions\n============\n\nA widget may prefer a given size for the allocation. This size, the\n\\em requisition, should be returned by the method\ndw::core::Widget::sizeRequestImpl. In the simplest case, this is\nindependent of the context, e.g. for an\nimage. dw::Image::sizeRequestImpl returns the following size:\n\n- If no buffer has yet been assigned (see dw::Image for more details),\n  the size necessary for the alternative text is returned. If no\n  alternative text has been set, zero is returned.\n\n- If a buffer has been assigned (by dw::Image::setBuffer), the root\n  size is returned (i.e. the original size of the image to display).\n\nThis is a bit simplified, dw::Image::sizeRequestImpl should also deal\nwith margins, borders and paddings, see dw::core::style.\n\nFrom the outside, dw::Image::sizeRequest should be called, which does\na bit of optimization. Notice that in dw::Image::sizeRequestImpl, no\noptimization like lazy evaluation is necessary, this is already done\nin dw::Image::sizeRequest.\n\nA widget, which has children, will likely call dw::Image::sizeRequest\non its children, to calculate the total requisition.\n\nThe caller (this is either the dw::core::Layout, or the parent\nwidget), may, but also may not consider the requisition. Instead, a\nwidget must deal with any allocation. (For example, dw::Image scales\nthe image buffer when allocated at another size.)\n\n\nSize Hints\n==========\n\n<div style=\"border: 2px solid #ff4040; margin-bottom: 0.5em;\npadding: 0.5em 1em; background-color: #fff0f0\"><b>Info:</b>\nSize hints have been removed, see \\ref dw-grows.</div>\n\n\nWidth Extremes\n==============\n\ndw::Table uses width extremes for fast calculation of column\nwidths. The structure dw::core::Extremes represents the minimal and\nmaximal width of a widget, as defined by:\n\n- the minimal width is the smallest width, at which a widget can still\n  display contents, and\n- the maximal width is the largest width, above which increasing the\n  width- does not make any sense.\n\nEspecially the latter is vaguely defined, here are some examples:\n\n- For those widgets, which do not depend on size hints, the minimal\n  and the maximal width is the inherent width (the one returned by\n  dw::core::Widget::sizeRequest).\n\n- For a textblock, the minimal width is the width of the widest\n  (unbreakable) word, the maximal width is the width of the total\n  paragraph (stretching a paragraph further would only waste space).\n  Actually, the implementation of dw::Textblock::getExtremesImpl is a\n  bit more complex.\n\n- dw::Table is an example, where the width extremes are calculated\n  from the width extremes of the children.\n\nHandling width extremes is similar to handling requisitions, a widget\nmust implement dw::core::Widget::getExtremesImpl, but a caller will\nuse dw::core::Widget::getExtremes.\n\n\nResizing\n========\n\nWhen the widget changes its size (requisition), it should call\ndw::core::Widget::queueResize. The next call of\ndw::core::Widget::sizeRequestImpl should then return the new\nsize. See dw::Image::setBuffer as an example.\n\nInterna are described in the code of dw::core::Widget::queueResize.\n\n<h3>Incremental Resizing</h3>\n\nA widget may calculate its size based on size calculations already\ndone before. In this case, a widget must exactly know the reasons, why\na call of dw::core::Widget::sizeRequestImpl is necessary. To make use\nof this, a widget must implement the following:\n\n1. There is a member dw::core::Widget::parentRef, which is totally\n   under control of the parent widget (and so sometimes not used at\n   all). It is necessary to define how parentRef is used by a specific\n   parent widget, and it has to be set to the correct value whenever\n   necessary.\n2. The widget must implement dw::core::Widget::markSizeChange and\n     dw::core::Widget::markExtremesChange, these methods are called in\n     two cases:\n     1. directly after dw::core::Widget::queueResize, with the\n        argument ref was passed to dw::core::Widget::queueResize,\n        and\n     2. if a child widget has called dw::core::Widget::queueResize,\n        with the value of the parent_ref member of this child.\n\nThis way, a widget can exactly keep track on size changes, and so\nimplement resizing in a faster way. A good example on how to use this\nis dw::Textblock.\n\n\nRules for Methods Related to Resizing\n=====================================\n\nWhich method can be called, when the call of another method is not\nfinished? These rules are important in two circumstances:\n\n1. To know which method can be called, and, especially, which methods\n   *must not* be called, within the implementation of\n   *sizeRequestImpl* (called by *sizeRequest*), *markSizeChange*, and\n   *markExtremesChange* (the latter two are called by *queueResize*).\n2. On the other hand, to make sure that the calls, which are allowed,\n   are handled correctly, especially in implementations of\n   *sizeRequestImpl*, *markSizeChange*, *markExtremesChange*\n\nGenerally, the rules defined below are, in case of doubt, rather\nstrict; when changing the rules, loosening is simpler than to tighten\nthem, since this will make it neccessary to review old code for calls\npreviously allowed but now forbidden.\n\nShort recap:\n\n- *QueueResize* directly calls *markSizeChange* and\n  *markExtremesChanges*, and queues an idle function for the actual\n  resizing (dw::core::Layout::resizeIdle). (The idle function is\n  called some time after *queueResize* is finished.)\n- The resize idle function first calls *sizeRequest*, then\n  *sizeAllocate*, for the toplevel widget.\n\nIn the following table, the rules are defined in detail. \"Within call\nof ...\" includes all methods called from the original method: the\nfirst row (*queueResize*) defines also the rules for\n*markExtremesChanges* and *markExtremesChanges*, and in the second row\n(*sizeAllocate*), even *sizeRequest* has to be considered.\n\n<div style=\"border: 2px solid #ff4040; margin-bottom: 0.5em;\npadding: 0.5em 1em; background-color: #fff0f0\"><b>Info:</b>\nNot up to date: *queueResize* can now be called recursively (so to\nspeak). See code there.</div>\n\n<table>\n   <tr>\n      <th>Within call of ... ↓\n      <th>... is call allowed of ... ? →\n      <th>queueResize\n      <th>sizeAllocate\n      <th>sizeRequest\n      <th>getExtremes\n   <tr>\n      <th colspan=2>queueResize\n      <td>No\n      <td>No<sup>1</sup>\n      <td>No<sup>1</sup>\n      <td>No<sup>1</sup>\n   <tr>\n      <th colspan=2>sizeAllocate\n      <td>Yes\n      <td>Only for children<sup>2</sup>\n      <td>Yes(?)\n      <td>Yes(?)\n   <tr>\n      <th colspan=2>sizeRequest\n      <td>Yes<sup>3</sup>\n      <td>No\n      <td>Limited<sup>4</sup>\n      <td>Limited<sup>4</sup>\n   <tr>\n      <th colspan=2>getExtremes\n      <td>Yes<sup>3</sup>\n      <td>No\n      <td>Limited<sup>4</sup>\n      <td>Limited<sup>4</sup>\n   <tr>\n     <td colspan=6><sup>1</sup>) Otherwise, since these other methods\nmay be call *queueResize*, the limitation that *queueResize* must not\ncall *queueResize* can be violated.\n\n<sup>2</sup>) Could perhaps be loosened as for *sizeRequest* and\n*getExtremes*, but there is probably no need.\n\n<sup>3</sup>) Therefore the distinction between *RESIZE_QUEUED* and\n*NEEDS_RESIZE*, and *EXTREMES_QUEUED* and *EXTREMES_CHANGED*,\nrespectively.\n\n<sup>4</sup>) Calls only for children are safe. In other cases, you\ntake a large responsibility to prevent endless recursions by\n(typically indirectly) calling *sizeRequest* / *getExtremes* for\ndirect ancestors.\n</table>\n\nFurthermore, *sizeAllocate* can only be called within a call of\ndw::core::Layout::resizeIdleId, so (if you do not touch dw::core) do\nnot call it outside of *sizeAllocateImpl*. The other methods can be\ncalled outsize; e.&nbsp;g. *sizeRequest* is called in\ndw::Textblock::addWidget.\n\nTo avoid painful debugging, there are some tests for the cases that\none method call is strictly forbidden while another method is called.\n\nThis could be done furthermore:\n\n- The tests could be refined.\n- Is it possible to define exacter rules, along with a proof that no\n  problems (like endless recursion) can occur?\n\n*/\n"
  },
  {
    "path": "devdoc/fltk-problems.doc",
    "content": "/** \\page fltk-problems Problems with FLTK\n\n<h2>dw::fltk::FltkViewport</h2>\n\nCurrent problems:\n\n<ul>\n<li> How should dw::fltk::FltkViewport::cancelQueueDraw be implemented?\n\n<li> If the value of a scrollbar is changed by the program, not the user,\n     the callback seems not to be called. Can this be assured?\n\n<li> The same for dw::fltk::FltkViewport::layout?\n\n<li> Also, the problems with the widgets seems to work. Also sure?\n\n<li> When drawing, clipping of 32 bit values is not working properly.\n\n<li> The item group within a selection widget (menu) should not be selectable.\n</ul>\n\n\n<h2>dw::fltk::FltkPlatform</h2>\n\n<ul>\n<li> There is the problem, that fltk::font always returns a font, the\n     required one, or a replacements. The latter is not wanted in all\n     cases, e.g. when several fonts are tested. Perhaps, this could be\n     solved by searching in the font list. <i>[This was true of fltk2.\n     What is the state of font handling now with fltk-1.3?]</i>\n\n<li> Distinction between italics and oblique would be nice\n     (dw::fltk::FltkFont::FltkFont).\n</ul>\n\n\n<h2>dw::fltk::ui::FltkCheckButtonResource</h2>\n\nGroups of Fl_Radio_Button must be added to one Fl_Group, which is\nnot possible in this context. There are two alternatives:\n\n<ol>\n<li>there is a more flexible way to group radio buttons, or\n<li>radio buttons are not grouped, instead, grouping (especially\n    unchecking other buttons) is done by the application.\n</ol>\n\n(This is mostly solved.)\n\n<h2>dw::fltk::FltkImgbuf</h2>\n\nAlpha transparency should be best abstracted by FLTK itself. If not,\nperhaps different implementations for different window systems could\nbe used. Then, it is for X necessary to use GCs with clipping masks.\n\n\n<h2>dw::fltk::ui::ComplexButton</h2>\n\nUnfortunately, FLTK does not provide a button with Fl_Group as parent, so\nthat children may be added to the button. dw::fltk::ui::ComplexButton does\nexactly this, and is, in an ugly way, a modified copy of the FLTK\nbutton.\n\nIt would be nice, if this is merged with the standard FLTK\nbutton. Furthermore, setting the type is strange.\n\nIf the files do not compile, it may be useful to create a new one from\nthe FLTK source:\n\n<ol>\n<li> Copy Fl_Button.H from FLTK to dw/fltkcomplexbutton.hh and\n     src/Button.cxx to dw/fltkcomplexbutton.cc.\n\n<li> In both files, rename \"Button\" to \"ComplexButton\". Automatic replacing\n     should work.\n\n<li> Apply the changes below.\n</ol>\n\nThe following changes should be applied manually.\n\n<h3>Changes in fltkcomplexbutton.hh</h3>\n\nFirst of all, the \\#define's for avoiding multiple includes:\n\n\\code\n-#ifndef fltk_ComplexButton_h // fltk_Button_h formerly\n-#define fltk_ComplexButton_h\n+#ifndef __FLTK_COMPLEX_BUTTON_HH__\n+#define __FLTK_COMPLEX_BUTTON_HH__\n\\endcode\n\nat the beginning and\n\n\\code\n-#endif\n+#endif // __FLTK_COMPLEX_BUTTON_HH__\n\\endcode\n\nat the end. Then, the namespace is changed:\n\n\\code\n-namespace fltk {\n+namespace dw {\n+namespace fltk {\n+namespace ui {\n\\endcode\n\nat the beginning and\n\n\\code\n-}\n+} // namespace ui\n+} // namespace fltk\n+} // namespace dw\n\\endcode\n\nat the end. Most important, the base class is changed:\n\n\\code\n-#include \"FL/Fl_Widget.H\"\n+#include <FL/Fl_Group.H>\n\\endcode\n\nand\n\n\\code\n-class FL_API ComplexButton : public Fl_Widget {\n+class ComplexButton: public Fl_Group\n+{\n\\endcode\n\nFinally, for dw::fltk::ui::ComplexButton::default_style, there is a\nnamespace conflict:\n\n\\code\n-  static NamedStyle* default_style;\n+  static ::fltk::NamedStyle* default_style;\n\\endcode\n\n<h3>Changes in fltkcomplexbutton.cc</h3>\n\nFirst, \\#include's:\n\n\\code\n\n #include <FL/Fl.H>\n-#include <FL/ComplexButton.h> // <FL/Fl_Button.H> formerly\n #include <FL/Fl_Group.H>\n #include <FL/Fl_Window.H>\n+\n+#include \"fltkcomplexbutton.hh\"\n\\endcode\n\nSecond, namespaces:\n\n\\code\n+using namespace dw::fltk::ui;\n\\endcode\n\nSince the base class is now Fl_Group, the constructor must be changed:\n\n\\code\n-ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) : Fl_Widget(x,y,w,h,l) {\n+ComplexButton::ComplexButton(int x,int y,int w,int h, const char *l) :\n+   Fl_Group(x,y,w,h,l)\n+{\n\\endcode\n\nFinally, the button must draw its children (end of\ndw::fltk::ui::ComplexButton::draw()):\n\n\\code\n+\n+  for (int i = children () - 1; i >= 0; i--)\n+     draw_child (*child (i));\n }\n\\endcode\n\n*/\n"
  },
  {
    "path": "devdoc/index.doc",
    "content": "/** \\mainpage\n\n<h2>Overview</h2>\n\nThis is a list of documents to start with:\n\n<ul>\n<li> \\ref lout\n<li> \\ref dw-overview (map at \\ref dw-map)\n</ul>\n\nCurrently, a document \\ref fltk-problems is maintained, ideally, it\nwill be removed soon.\n\n<h2>Historical</h2>\n\n<h3>Replacements for GTK+ and GLib</h3>\n\nThere are several classes etc., which are used for tasks formerly (in the GTK+\nversion of dillo) achieved by GtkObject (in 1.2.x, this is part of Gtk+) and\nGLib. For an overview on all this, take a look at \\ref lout.\n\nGtkObject is replaced by the following:\n\n<ul>\n<li> lout::object::Object is a common base class for many classes used\n     dillo. In the namespace lout::object, there are also some more common\n     classes and interfaces.\n\n<li> A sub class of lout::object::Object is\n     lout::identity::IdentifiableObject, which allows to determine the\n     class at run-time (equivalent to GTK_CHECK_CAST in GtkObject).\n\n<li> For signals, there is the namespace lout::signal.\n</ul>\n\nHash tables, linked lists etc. can be found in the lout::container namespace,\nseveral useful macros from GLib have been implemented as inline functions\nin the lout::misc namespace.\n\nAs an alternative to the macros defined in list.h, there is also a template\nclass, lout::misc::SimpleVector, which does the same.\n\n<h3>Changes in Dw</h3>\n\nIf you have been familiar with Dw before, take a look at \\ref dw-changes.\n\n*/\n"
  },
  {
    "path": "devdoc/lout.doc",
    "content": "/** \\page lout Lots of Useful Tools\n\nIn the \"lout\" directory, there are some common base functionality for\nC++. Most is described as doxygen comments, this text gives an\noverview.\n\n<h2>Common Base Class</h2>\n\nMany classes are derived from lout::object::Object, which defines some\ngeneral methods. See there for more information.\n\nFor the case, that you need primitive C++ types, there are some\nwrappers:\n\n<table>\n<tr><th>C++ Type         <th>Wrapper Class\n<tr><td>void*            <td>lout::object::Pointer\n<tr><td>specific pointer <td>lout::object::TypedPointer (template class)\n<tr><td>int              <td>lout::object::Integer\n<tr><td>const char*      <td>lout::object::ConstString\n<tr><td>char*            <td>lout::object::String\n</table>\n\n\n<h2>Containers</h2>\n\nIn the namespace lout::container, several container classes are defined,\nwhich all deal with instances of lout::object::Object.\n\n<h3>Untyped Containers</h3>\n\nIn lout::container::untyped, there are the following containers:\n\n<ul>\n<li>lout::container::untyped::Vector, a dynamically increases array,\n<li>lout::container::untyped::List, a linked list,\n<li>lout::container::untyped::HashTable, a hash table, and\n<li>lout::container::untyped::Stack, a stack.\n</ul>\n\nAll provide specific methods, but since they have a common base class,\nlout::container::untyped::Collection, they all provide iterators, by the\nmethod lout::container::untyped::Collection::iterator.\n\n<h3>Typed Containers</h3>\n\nlout::container::typed provides wrappers for the container classes defined\nin lout::container::untyped, which are more type safe, by using C++\ntemplates.\n\n\n<h2>Signals</h2>\n\nFor how to connect objects at run-time (to reduce dependencies), take a\nlook at the lout::signal namespace.\n\nThere is also a base class lout::signal::ObservedObject, which implements\nsignals for deletion.\n\n\n<h2>Debugging</h2>\n\nIn debug.hh, there are some some useful macros for debugging messages,\nsee the file for mor informations.\n\n\n<h2>Identifying Classes at Runtime</h2>\n\nIf the class of an object must be identified at runtime,\nlout::identity::IdentifiableObject should be used as the base class,\nsee there for more details.\n\n\n<h2>Miscellaneous</h2>\n\nThe lout::misc namespace provides several miscellaneous stuff:\n\n<ul>\n<li> In some contexts, it is necessary to compare objects\n     (less/greater), for this, also lout::misc::Comparable must be\n     implemented. For example., lout::container::untyped::Vector::sort and\n     lout::container::typed::Vector::sort cast the elements to\n     lout::misc::Comparable. This can be mixed with lout::object::Object.\n<li> lout::misc::SimpleVector, a simple, template based vector class\n     (not depending on lout::object::Object) (a variant for handling a\n     special case in an efficient way is lout::misc::NotSoSimpleVector),\n<li> lout::misc::StringBuffer, class for fast concatenation of a large number\n     of strings,\n<li> lout::misc::BitSet implements a bitset.\n<li> useful (template) functions (lout::misc::min, lout::misc::max), and\n<li> some functions useful for runtime checks (lout::misc::assert,\n     lout::misc::assertNotReached).\n</ul>\n\n*/\n"
  },
  {
    "path": "devdoc/rounding-errors.doc",
    "content": "/** \\page rounding-errors How to Avoid Rounding Errors\n\n(Probably, this is a standard algorithm, so if someone knows the name,\ndrop me a note.)\n\nIf something like\n\n\\f[y_i = {x_i a \\over b}\\f]\n\nis to be calculated, and all numbers are integers, a naive\nimplementation would result in something, for which\n\n\\f[\\sum y_i \\ne {(\\sum x_i) a \\over b}\\f]\n\nbecause of rounding errors, due to the integer division. This can be\navoided by transforming the formula into\n\n\\f[y_i = {(\\sum_{j=0}^{j=i} x_j) a \\over b} - \\sum_{j=0}^{j=i-1} y_j\\f]\n\nOf corse, when all \\f$y_i\\f$ are calculated in a sequence,\n\\f$\\sum_{j=0}^{j=i} x_j\\f$ and \\f$\\sum_{j=0}^{j=i-1} y_j\\f$ can be\naccumulated in the same loop. Regard this as sample:\n\n\\code\nint n, x[n], a, b; // Should all be initialized.\nint y[n], cumX = 0, cumY = 0;\n\nfor (int i = 0; i < n; i++) {\n   cumX += x[i]\n   y[i] = (cumX * a) / b - cumY;\n   cumY += y[i];\n}\n\\endcode\n\n*/\n"
  },
  {
    "path": "devdoc/uml-legend.doc",
    "content": "/** \\page uml-legend UML Legend\n\nThis page describes the notation for several diagrams used in the\ndocumentation, which is a slight variation of UML.\n\n\n<h2>Classes</h2>\n\nClasses are represented by boxes, containing there names:\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   fontname=Helvetica; fontsize=8;\n   \"Concrete Class\";\n   \"Abstract Class\" [color=\"#a0a0a0\"];\n   Interface [color=\"#ff8080\"];\n}\n\\enddot\n\n(In most cases, the attributes and operations are left away, for\nbetter readibility. Just click on it, to get to the detailed\ndescription.)\n\nOf course, in C++, there are no interfaces, but here, we call a class,\nwhich has only virtual abstract methods, and so does not provide any\nfunctionality, an interface.\n\nTemplates get a yellow background color:\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10,\n         fillcolor=\"#ffffc0\", style=\"filled\"];\n   fontname=Helvetica; fontsize=8;\n   \"Concrete Class Template\";\n   \"Abstract Class Template\" [color=\"#a0a0a0\"];\n   \"Interface Template\" [color=\"#ff8080\"];\n}\n\\enddot\n\n\n<h2>Objects</h2>\n\nIn some cases, an examle for a concrete constellation of objects is\nshown. An object is represented by a box containing a name and the\nclass, separated by a colon.\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"open\", labelfontname=Helvetica, labelfontsize=10,\n         color=\"#404040\", labelfontcolor=\"#000080\"];\n   fontname=Helvetica; fontsize=10;\n\n   \"x: A\" -> \"y1: B\";\n   \"x: A\" -> \"y2: B\";\n}\n\\enddot\n\nThe names (\\em x, \\em y, and \\em z) are only meant within the context\nof the diagram, there needs not to be a relation to the actual names\nin the program. They should be unique within the diagram.\n\nClasses and objects may be mixed in one diagram.\n\n\n<h2>Associations</h2>\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"open\", labelfontname=Helvetica, labelfontsize=10,\n         color=\"#404040\", labelfontcolor=\"#000080\",\n         fontname=Helvetica, fontsize=10, fontcolor=\"#000080\"];\n   fontname=Helvetica; fontsize=10;\n   A -> B [headlabel=\"*\", taillabel=\"1\", label=\"x\"];\n}\n\\enddot\n\nIn this example, one instance of A refers to an arbitrary number of B\ninstances (denoted by the \"*\"), and each instance of B is referred by\nexactly one (\"1\") A. The label \\em x is the name of the association,\nin most cases the name of the field, e.g. A::x.\n\nPossible other values for the \\em multiplicity:\n\n<ul>\n<li> a concrete number, in most cases \"1\",\n<li> a range, e.g. \"0..1\",\n<li> \"*\", denoting an arbitrary number.\n</ul>\n\n\n<h2>Implementations and Inheritance</h2>\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"none\", dir=\"both\", arrowtail=\"empty\",\n         labelfontname=Helvetica, labelfontsize=10, color=\"#404040\",\n         labelfontcolor=\"#000080\"];\n   fontname=Helvetica; fontsize=10;\n   A[color=\"#ff8080\"];\n   B[color=\"#ff8080\"];\n   C;\n   D;\n   A -> B;\n   A -> C [style=\"dashed\"];\n   C -> D;\n}\n\\enddot\n\nIn this example,\n\n<ul>\n<li> the interface B extends the interface A,\n<li> the class C implements the interface A, and\n<li> the class D extends the class C.\n</ul>\n\n\n<h2>Template Instantiations</h2>\n\nTemplate instantiations are shown as own classes/interfaces, the\ninstantiation by the template is shown by a yellow dashed arrow:\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n         labelfontname=Helvetica, labelfontsize=10, color=\"#404040\",\n         labelfontcolor=\"#000080\"];\n   fontname=Helvetica; fontsize=10;\n\n   A[color=\"#ff8080\"];\n   B[color=\"#ff8080\"];\n   C[color=\"#ff8080\", fillcolor=\"#ffffc0\", style=\"filled\"];\n   C_A[color=\"#ff8080\", label=\"C \\<A\\>\"];\n   C_B[color=\"#ff8080\", label=\"C \\<A\\>\"];\n   D;\n\n   C -> C_A [arrowhead=\"open\", arrowtail=\"none\", style=\"dashed\",\n             color=\"#808000\"];\n   C -> C_B [arrowhead=\"open\", arrowtail=\"none\", style=\"dashed\",\n             color=\"#808000\"];\n   A -> C_A;\n   B -> C_B;\n   C_A -> D [style=\"dashed\"];\n}\n\\enddot\n\nIn this example, the interface template C uses the template argument\nas super interface.\n\n\n<h2>Packages</h2>\n\nPackages are presented by dashed rectangles:\n\n\\dot\ndigraph G {\n   node [shape=record, fontname=Helvetica, fontsize=10];\n   edge [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n         labelfontname=Helvetica, labelfontsize=10, color=\"#404040\",\n         labelfontcolor=\"#000080\"];\n   fontname=Helvetica; fontsize=10;\n\n   subgraph cluster_1 {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n      label=\"package 1\";\n\n      A;\n      B [color=\"#a0a0a0\"];\n   }\n\n   subgraph cluster_2 {\n      style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n      label=\"package 2\";\n\n      C;\n      D [color=\"#a0a0a0\"];\n      E\n   }\n\n   A -> C;\n   B -> D;\n   D -> E;\n   E -> A [arrowhead=\"open\", arrowtail=\"none\"];\n}\n\\enddot\n\nPackages may be nested.\n\n*/"
  },
  {
    "path": "dillo-install-hyphenation",
    "content": "#!/usr/bin/env perl\nuse POSIX;\nuse File::Basename;\nuse Net::FTP;\nuse Getopt::Long;\n\n$host = \"mirrors.dotsrc.org\";\n$basesourcedir = \"/ctan\";\n$sourcedir = \"\";\n\nsub ftpmessage {\n   # I'm not sure whether $ftp->message is supposed to end with \"\\n\"\n   # or not. To be sure (and have nicer output on the screen), it is\n   # removed here.\n   my $x = $ftp->message;\n   chomp $x;\n   return $x;\n}\n\n# Determine ${prefix}, for the default target directory for pattern\n# files. Two different strategies ...\n$makefile = (dirname $0) . \"/Makefile\";\nif (-e $makefile) {\n   # 1. Makefile exists in the same directory, so it can be assumed\n   # that this script is not yet installed, but called from the source\n   # directory. Search Makefile for prexix. \n   open MF, $makefile or die \"Cannot open $makefile: $!\";\n   while (<MF>) {\n      if (/^\\s*prefix\\s*=\\s*(.*)\\s*$/) {\n         $prefix = $1;\n      }\n   }\n   close MF;\n\n   if ($prefix eq \"\") {\n      die \"Prefix not defined in $makefile.\";\n   }\n} else {\n   # 2. Assume that this is script is installed, so its path contains\n   # the prefix.\n   if ($0 =~ /(.*)\\/bin\\/[^\\/]+$/) {\n      $prefix = $1;\n   } else {\n      die \"Failed to determine prefix from $0.\";\n   }\n}\n\n$targetdir = \"$prefix/lib/dillo-plus/hyphenation\";\n\nif (!GetOptions (\"host=s\" => \\$host,\n                 \"basesourcedir=s\" => \\$basesourcedir,\n                 \"sourcedir=s\" => \\$sourcedir,\n                 \"targetdir=s\" => \\$targetdir)\n    || @ARGV == 0) {\n   print \"Usage: $0 [OPTIONS] LANG [LANG ...]\\n\\n\";\n   print <<EOT;\nDownload and install hyphenation patterns from CTAN for different languages,\nvia FTP. Languages are specified as defined by ISO 639-1 (\"en\" for English\netc.).\n\nIf there are multiple pattern files for a language (and so the filename is not\n\"hyph-LANG.pat.txt\"), you must specify the respective part of the filename,\ne. g. \"en-gb\" or \"en-us\". In this case, the extra part (\"-gb\" or \"-us\") is\nautomatically removed.\n\nIf you are not sure, simply specify the language code (\"en\" in this example);\nyou will then given a list of existing pattern files.\n\nOptions:\n   -h, --host=HOST            the host to connect to (default:\n                              mirrors.dotrc.org)\n   -s, --sourcedir=DIR        the directory on the FTP server\n   -b, --basesourcedir=DIR    alternatively, the base directory, after which\n                              /language/hyph-utf8/tex/generic/hyph-utf8...\n                              .../patterns/txt is added (default: /ctan/)\n   -t, --targetdir=DIR        where to install the hyphenation patterns\n                              (default: where they are read by dillo)\nEOT\n} else {\n   if ($sourcedir eq \"\") {\n      $sourcedir =\n         \"$basesourcedir/language/hyph-utf8/tex/generic/hyph-utf8/patterns/txt\";\n   }\n\n   if (!-e $targetdir) {\n      mkdir $targetdir or die \"Cannot create directory $targetdir: $!\";\n      print \"Created $targetdir.\\n\";\n   }\n\n   # Connect to CTAN FTP server, change to the directory where the\n   # patterns lie, and read files list (which may be useful later).\n   $ftp = Net::FTP->new($host,Timeout=>240)\n      or die \"Cannot connect to $host: $@\";\n   $ftp->login() or die \"Cannot login: \", ftpmessage;\n   $ftp->cwd($sourcedir)\n      or die \"Cannot change to directory $sourcedir: \", ftpmessage;\n   @files = $ftp->ls or die \"Cannot read directory: \", ftpmessage;\n\n   # Finally, read pattern files.\n   foreach $arg (@ARGV) {\n      if ($arg =~ /^([a-z]+)-.*$/) {\n         # More files per language, e. g. \"en-gb\".\n         $lang = $1;\n      } else {\n         # One file per language, e. g. \"ru\".\n         $lang = $arg;\n      }\n      \n      # First, donwload the pattern file to a temporary file.\n      $tmppat = tmpnam();\n      if ($ftp->get (\"hyph-$arg.pat.txt\", $tmppat)) {\n         printf (\"Successfully downloaded pattern file for \\\"$arg\\\".\\n\");\n         \n         # Search for a licence file. (Only a warning, when it does\n         # not exist.)\n         $tmplic = tmpnam();\n         $licfound = 0;\n         if ($ftp->get (\"hyph-$arg.lic.txt\", $tmplic)) {\n            $licfound = 1;\n         } else {\n            print \"Warning: Cannot download license file for \\\"$arg\\\": \",\n              ftpmessage, \"\\n\";\n         }\n         \n         # Combine both, licence and pattern, to the final pattern\n         # file.\n         $outfile = \"$targetdir/$lang.pat\";\n         open OUT, \"> $outfile\" or die \"Cannot open $outfile: $!\";\n         \n         if ($licfound) {\n            print OUT\n               \"% Licence from ftp://$host$sourcedir/hyph-$arg.lic.txt\\n\";\n            print OUT \"%\\n\";\n            open IN, $tmplic or die \"Cannot open $tmplic: $!\";\n            while (<IN>) {\n               # Each line from the licence file must be a comment.\n               if (!/^%/) {\n                  print OUT \"% \";\n               }\n               print OUT;\n            }\n            close IN;\n            unlink $tmplic;\n\n            print OUT \"%\\n\";\n         }\n         \n         print OUT \"% Patterns from ftp://$host$sourcedir/hyph-$arg.pat.txt\\n\";\n         print OUT \"%\\n\";\n         open IN, $tmppat or die \"Cannot open $tmppat: $!\";\n         while (<IN>) {\n            print OUT;\n         }\n         close IN;\n         unlink $tmppat;\n         \n         close OUT;\n      } else {\n         # Not found. If a single language was specified (e. g. \"en\"),\n         # search for possibilities.\n         print \"Error: Cannot download pattern file for \\\"$arg\\\": \",\n            ftpmessage, \"\\n\";\n         if ($lang eq $arg) {\n            print \"Try one of these:\\n\";\n            foreach(@files) {\n               if (/^hyph-($lang-.*)\\.pat\\.txt$/) {\n                  print \"   $1\\n\";\n               }\n            }\n         }\n      }\n   }\n   \n   $ftp->quit;\n}\n"
  },
  {
    "path": "dillorc",
    "content": "# dillorc\n# Sample dillo initialization file.\n#\n# Lines that start with a '#' are comments.\n# \"#option=...\" shows the built-in default.\n# \"# option=...\" is an additional example.\n# \"option=...\" overrides the built-in value.\n\n#-------------------------------------------------------------------------\n#                             FIRST SECTION                             :)\n#-------------------------------------------------------------------------\n\n# Set the desired initial browser size\n# geometry=650x545+0+20\n#geometry=780x580\n\n# Change this (and the following option) if you want to have text-only browsing\n# from the start. (While browsing, this can be changed from the tools/settings\n# menu.)\n#load_images=YES\n\n# Change this if you want background images to be loaded initially.\n# (While browsing, this can be changed from the tools/settings menu.)\n#load_background_images=NO\n\n# Change this if you want to disable ithe use of cookies initially.\n# (While browsing, this can be changed from the tools/settings menu.)\n#use_cookies=YES\n\n# Change this if you want to disable loading of CSS stylesheets initially.\n# (While browsing, this can be changed from the tools/settings menu.)\n#load_stylesheets=YES\n\n# Change this if you want to disable parsing of embedded CSS initially.\n# (While browsing, this can be changed from the tools/settings menu.)\n#parse_embedded_css=YES\n\n# Change this if you want to enable loading of reader mode CSS initially.\n# (While browsing, this can be changed from the tools/settings menu.)\n#load_reader_mode_css=NO\n\n# Change the buffering scheme for drawing\n# 0 no double buffering - useful for debugging\n# 1 light buffering using a single back buffer for all windows\n# 2 full fltk-based double buffering for all windows\n#buffered_drawing=1\n\n# Video player (e.g. for Youtube videos)\nmedia_player=mpv --user-agent=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36\" --ytdl-format=\"bestvideo[height<=?360]+bestaudio/best\"\n\n# Set your default directory for download/save operations\n#save_dir=/tmp\n\n#-------------------------------------------------------------------------\n#                           RENDERING SECTION\n#-------------------------------------------------------------------------\n\n# Default fonts:\n#\n# If FLTK has been configured with Xft enabled (the default), you can use\n# scalable fonts such as DejaVu or Liberation (try running\n# \"fc-list : family | cut -d ',' -f 2 | sort\").\n#font_serif=\"DejaVu Serif\"\n#font_sans_serif=\"DejaVu Sans\"\nfont_cursive=\"DejaVu Serif\"\n#font_fantasy=\"DejaVu Sans\"\n#font_monospace=\"DejaVu Sans Mono\"\n#\n# Otherwise, use bitmapped fonts like the following (for a list, try running\n# \"xlsfonts -fn *-iso10646-1 | grep -v -e -0-0 | cut -d - -f 3 | sort | uniq\").\n# font_serif=\"times\"\n# font_sans_serif=\"helvetica\"\n# font_cursive=\"helvetica\"\n# font_fantasy=\"helvetica\"\n# font_monospace=\"courier\"\n\n# All font sizes are scaled by this value\n# font_factor=1.5\n#font_factor=1.0\n\n# Maximum font size in pixels\n#font_max_size=100\n\n# Minimum font size in pixels\n#font_min_size=6\n\n# Show tooltip popups for HTML title attributes\n#show_tooltip=YES\n\n# Set this to YES to limit the word wrap width to the viewport width\n#limit_text_width=NO\n\n# If this is set to YES, all CSS size specifications are adjusted so that\n# all contents can be displayed. (Except for tables, see below.)\n#adjust_min_width=YES\n\n# If this is set to YES, all CSS size specifications for tables are\n# adjusted so that all contents can be displayed. This is seperated\n# from \"adjust_min_width\" so that it is able to mimic Firefox, which\n# differenciates between tables and, say, textblocks (in some cases).\n#adjust_table_min_width=YES\n\n#-------------------------------------------------------------------------\n#                               PENALTIES\n#-------------------------------------------------------------------------\n\n# Penalties are used to control good and bad break points. The bigger\n# the penalty for a given break point, the less likely the line is\n# broken here. \"inf\" means that breaking is prohibited, \"-inf\" means\n# that a line *must* be broken here. (The latter should not be used\n# here, however.) Normal spaces get a penalty of 0. The exact\n# definition can be found in doc/dw-line-breaking.doc.\n\n# Penalties for hyphenation breaks; this covers automatic hyphenation,\n# soft hyphens, and unconditional hyphens. Since hyphenation should\n# rather be avoided, the default values are larger than 0.\n\n# This is used for hyphenation points, when there is no hyphen or dash\n# before:\n#penalty_hyphen = 1\n\n# This is used for hyphenation points, when the line before ends\n# already with a hyphen or a dash. Consequent lines ending with\n# hyphens or dashes should be avoided, so this value is bigger than\n# \"penalty_hyphen\":\n#penalty_hyphen_2 = 8\n\n# The same for a break right of an em-dash, when there are no spaces\n# surrounding it (as in English). The default values are the same as\n# for hyphens:\n#penalty_em_dash_right = 1\n#penalty_em_dash_right_2 = 8\n\n# Penalty for a break *left* of an em-dash. Since a line ending with\n# an em-dash (and so breaking right of the em-dash) looks better than\n# a line beginning with an em-dash (breaking left of an em-dash), the\n# default value is bigger than \"penalty_em_dash_right\":\n#penalty_em_dash_left = 8\n\n# Notice that there is no \"penalty_em_dash_left_2\", since breaking\n# left of an em-dash makes the line *begin*, not *end* with a dash.\n\n# This factor is multiplied with the line height to get the\n# stretchability of a non-justified line. The larger this factor (and\n# thus, the stretchability), the less likely the words are hyphenated;\n# so you can use this value to control hyphenation of non-justified\n# text.\n#stretchability_factor=1\n\n#-------------------------------------------------------------------------\n#                            PARSING SECTION\n#-------------------------------------------------------------------------\n\n# If you prefer more accurate HTML bug diagnosis over better rendering\n# (page authors and webmasters) set the following to \"NO\".\n#\n#w3c_plus_heuristics=YES\n\n\n#-------------------------------------------------------------------------\n#                            NETWORK SECTION\n#-------------------------------------------------------------------------\n\n# Set the start page.\n# start_page=\"about:blank\"\n# start_page=\"https://dillo-browser.github.io/\"\n# start_page=\"file:/home/jcid/custom_page.html\"\n#start_page=\"about:splash\"\n\n# Set the home location\n# home=\"file:/home/jcid/HomePage/Home.html\"\n#home=\"https://dillo-browser.github.io/old/\"\n\n# Set the URLs used by the web search dialog.\n# \"%s\" is replaced with the search keywords separated by '+'.\n# Format: search_url=\"[prefix ][<label> ]<url>\"\n# You can enable multiple search_url strings at once and select from among\n# them at runtime, with the first being the default.\n# (the prefix serves to search from the Location Bar. e.g. \"dd dillo image\")\nsearch_url=\"dd DuckDuckGo https://duckduckgo.com/lite/?kp=-1&q=%s\"\nsearch_url=\"se SearX (searx.be) https://searx.be/search?q=%s\"\nsearch_url=\"se2 SearX (searxng.ch) https://searxng.ch/search?q=%s\"\nsearch_url=\"se3 SearX (searxng.ca) https://searxng.ca/search?q=%s\"\nsearch_url=\"se4 SearX (priv.au) https://priv.au/search?q=%s\"\nsearch_url=\"ya Yandex https://yandex.com/search/?text=%s\"\nsearch_url=\"wk Wikipedia https://www.wikipedia.org/w/index.php?search=%s&go=Go\"\nsearch_url=\"wt Wiktionary https://www.wiktionary.org/w/index.php?search=%s&go=Go\"\nsearch_url=\"wc Wikicommons https://commons.wikimedia.org/w/index.php?search=%s&go=Go\"\nsearch_url=\"wb Wikibooks https://www.wikibooks.org/w/index.php?search=%s&go=Go\"\nsearch_url=\"wv Wikiversity https://www.wikiversity.org/w/index.php?search=%s&go=Go\"\nsearch_url=\"wy Wikivoyage https://www.wikivoyage.org/w/index.php?search=%s&go=Go\"\nsearch_url=\"ar Internet Archive https://archive.org/search.php?query=%s\"\nsearch_url=\"fd Free Dictionary https://www.thefreedictionary.com/%s\"\nsearch_url=\"sp Startpage https://www.startpage.com/do/search?query=%s\"\nsearch_url=\"mn Marginalia https://search.marginalia.nu/search?query=%s\"\nsearch_url=\"te Teclis https://teclis.com/search?q=%s\"\nsearch_url=\"gg Google https://www.google.com/search?ie=UTF-8&oe=UTF-8&q=%s\"\nsearch_url=\"ge GeminiSpace gemini://geminispace.info/search?q=%s\"\nsearch_url=\"lg LibGen http://libgen.is/search.php?req=%s\"\nsearch_url=\"gr GoodReads https://www.goodreads.com/search?query=%s\"\nsearch_url=\"pb The Pirate Bay https://thepiratebay.party/search/%s\"\nsearch_url=\"tw Twitter https://nitter.net/search?f=users&q=%s\"\nsearch_url=\"un Unsplash https://unsplash.com/s/photos/%s\"\nsearch_url=\"dv Deviant Art https://www.deviantart.com/search?q=%s\"\nsearch_url=\"dw Debian Wiki https://wiki.debian.org/FrontPage?action=fullsearch&context=180&value=%s\"\nsearch_url=\"aw Arch Wiki https://wiki.archlinux.org/index.php?search=%s&title=Special%3ASearch\"\nsearch_url=\"gw Gentoo Wiki https://wiki.gentoo.org/index.php?title=Special%3ASearch&search=%s\"\nsearch_url=\"ow OpenSuse Wiki https://en.opensuse.org/index.php?search=%s\"\nsearch_url=\"sl Slackware Docs https://docs.slackware.com/%s?do=search&q=%s\"\nsearch_url=\"db Debian Package https://packages.debian.org/search?keywords=%s&s:\"\nsearch_url=\"ap Arch Package https://archlinux.org/packages/?sort=&q=%s&maintainer=&flagged=\"\nsearch_url=\"fp Fedora Package https://packages.fedoraproject.org/search?query=%s\"\nsearch_url=\"ob OpenBSD Package https://openbsd.app/?search=%s\"\nsearch_url=\"os OpenSuse Package https://software.opensuse.org/search?q=%s&baseproject=openSUSE%3AFactory\"\nsearch_url=\"fb FreeBSD Man https://man.freebsd.org/cgi/man.cgi?query=%s&sektion=0\"\n\n# If set, dillo will ask web servers to send pages in this language.\n# This setting does NOT change dillo's user interface.\n# Format explained: www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4\n# Language-REGION values: www.iana.org/assignments/language-subtag-registry\n# (by default, no Accept-Language header is sent)\n# http_language=\"de\"\n# http_language=\"pt-BR\"\n# http_language=\"en-US,en;q=0.5\"\n\n# Maximum number of simultaneous TCP connections to a single server or proxy.\n# http_max_conns=6\n\n# If enabled, Dillo will reuse HTTP connections to a server or proxy when\n# possible rather than making a new connection for every request for a new\n# page/image/stylesheet.\n#http_persistent_conns=YES\n\n# This mechanism allows servers to specify that they are only to be contacted\n# through HTTPS and not HTTP.\n#\n# Overall, this is a valuable security measure against TLS stripping\n# attacks, etc., but in principle a site could contrive to use this as a\n# tracking mechanism. The term is \"HSTS super cookie\", although note that these\n# HSTS directives are not saved between browser sessions.\n#http_strict_transport_security=YES\n\n# Set the proxy information for http/https.\n# Note that the http_proxy environment variable overrides this setting.\n# WARNING: FTP and downloads plugins use wget. To use a proxy with them,\n#          you will need to configure wget accordingly. See\n#          http://www.gnu.org/software/wget/manual/html_node/Proxies.html\n# http_proxy=\"http://localhost:8080/\"\n#(by default, no proxy is used)\n\n# If you need to provide a  user/password pair for the proxy,\n# set the proxy user name here and Dillo will ask for the password later.\n# http_proxyuser=\"joe\"\n#(by default, no proxy is used)\n\n# Set the domains to access without proxy\n# no_proxy = \".hola.com .mynet.cl .hi.de\"\n#no_proxy=\"localhost 127.0.0.1\"\n\n# Set the HTTP Referer (sic) header.\n# Note that there is no option to reveal the page that you came from because it\n# would endanger your privacy. 'host' and 'path' allow you to pretend that the\n# link you followed was on the same site that you're going to.\n# none  : Don't send any Referer header at all.\n# host  : Send the requested URI's hostname.\n# path  : Send the requested URI's host and path.\n#http_referer=host\n\n# Set the HTTP User-Agent header.\n# This can be useful for privacy and for working around servers who think\n# Dillo is less capable than it really is. However, if you pretend to use a\n# different browser, servers may send you pages that work with the features\n# and bugs of that other browser -- or even disallow access in cases like\n# wget or googlebot. Remember this before submitting bug reports.\n#\n# See http://zytrax.com/tech/web/browser_ids.htm for a compilation of strings\n# or https://www.useragents.me/.\n#\n# http_user_agent=\"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:28.0) Gecko/20100101 Firefox/28.0\"\n# http_user_agent=\"Wget/1.13.4 (linux-gnu)\"\n#\n# If not set \"Dillo/\"+current_version_number is used\n#\n# To prevent some browser fingerprinting and avoid some filters the default\n# string is set to a common user agent (in this case: Windows 10, Chrome 108):\nhttp_user_agent=\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36\"\n\n#-------------------------------------------------------------------------\n#                            COLORS SECTION\n#-------------------------------------------------------------------------\n\n# Set the page background color\n# bg_color=gray\n# bg_color=0xd6d6c0\n#bg_color=0xdcd1ba\nbg_color=0x778899\n\n# If your eyes suffer with white backgrounds, change this.\n#allow_white_bg=YES\n\n# If allow_white_bg is set to NO, white backgrounds are replaced by\n# this color.\n#white_bg_replacement=0xe0e0a3\n\n# When set to YES, the page author's visited link color may be overridden\n# to allow better contrast with text/links/background\n#contrast_visited_color=YES\n\n\n#-------------------------------------------------------------------------\n#                        USER INTERFACE SECTION\n#-------------------------------------------------------------------------\n\n# UI theme\n# \"none\" is the default FLTK appearance, which \"resembles old Windows...and\n# old GTK/KDE\".\n# \"plastic\" \"is inspired by the Aqua user interface on Mac OS X\".\n# \"gtk+\" \"is inspired by the Red Hat Bluecurve theme\".\n#\n# If you have fltk-1.3.3 or newer, you can specify \"gleam\", which\n# is \"a sort of Clearlooks Glossy scheme\".  (\"fltk-config --version\")\n#theme=none\n# theme=gtk+\n# theme=plastic\n\n# UI colors\n# Note that FLTK may sometimes override colors, generally for contrast and\n# readability.\n#\n# ui_fg_color, ui_main_bg_color, ui_text_bg_color, and ui_selection_color\n# map to concepts in the underlying FLTK toolkit which are described as:\n# \"the default foreground color...used for labels and text\", \"default\n# background color\", \"the default background color for text, list, and\n# valuator widgets\", and \"the default selection/highlight color\". They\n# sometimes have other uses in the more complex FLTK widgets.\n#\n# ui_button_highlight_color is the background used when the mouse cursor is\n# over a button. By default, this is a lightened version of the main\n# background color.\n#\n# ui_tab_active_fg_color and ui_tab_active_bg_color are used for the current\n# tab. By default, they are the main foreground color and the text background\n# color, respectively.\n#\n# ui_tab_fg_color and ui_tab_bg_color are used for the other tabs. By default,\n# they are the main foreground color and the main background color,\n# respectively.\n#\n# Note to packagers: leaving these variables for the system to guess\n# gives different results in different environments, so we played it safe\n# by defining the traditional colors.  Please choose the color theme that\n# better fits your distro.\n#\n#ui_fg_color=black\n#ui_main_bg_color=silver\n#ui_text_bg_color=white\n#ui_selection_color=navy\n#\n# Background used when the mouse cursor is over a button.\n#ui_button_highlight_color=(by default, the main background color, lightened)\n#\n# Colors for the current tab.\n#ui_tab_active_bg_color=(by default, the text background color)\n#ui_tab_active_fg_color=(by default, the main foreground color)\n#\n# Colors for the other tabs.\n#ui_tab_bg_color=(by default, the main background color)\n#ui_tab_fg_color=(by default, the main foreground color)\n\n#\n# Gray theme (traditional)\n#\n#ui_fg_color=black\n#ui_main_bg_color=#c6c6c6\n#ui_text_bg_color=#bfdabf\n#ui_selection_color=#191970\n#ui_button_highlight_color=#a9a9a9\n#ui_tab_active_bg_color=#87aca7\n#ui_tab_active_fg_color=black\n#ui_tab_bg_color=#b7beb7\n\n#\n# Breeze-light theme\n#\n# unused from /usr/share/themes/Breeze/gtk-2.0/gtkrc:\n#  insensitive_base_color(e7e7e7) tooltip_bg_color(f7f7f7)\n#  insensitive_fg_color(a6a8a9) button_insensitive_fg_color(aeafb0)\n#ui_fg_color=232629# text_color button_fg_color tooltip_fg_color\n#ui_main_bg_color=0xdee0e2# matches screenshots.debian and system\n#ui_text_bg_color=0xfeffff# base_color but modified to avoid whitereplace\n#ui_selection_color=0x3daee9# selected_bg_color button_active\n#ui_button_highlight_color=0xceced0# matches system\n#ui_tab_active_bg_color=0xeff0f1# bg_color\n#ui_tab_active_fg_color=0x232629# text_color button_fg_color tooltip_fg_color\n#ui_tab_bg_color=0xdee0e2# matches screenshots.debian and system\n\n#\n# Adwaita-dark theme\n#\n# copied from /usr/share/themes/Adwaita-dark/gtk-2.0/gtkrc\n#ui_fg_color=feffff# text_color slightly off-white to avoid whitereplace\n#ui_main_bg_color=0x2d3234# insensitive_bg_color\n#ui_text_bg_color=0x232729# base_color\n#ui_selection_color=0x215d9c# selected_bg_color\n#ui_button_highlight_color=0x262b2d# menu_color\n#ui_tab_active_bg_color=0x232729# base_color\n#ui_tab_active_fg_color=0xfeffff# text_color slightly off-white to avoid whitereplace\n#ui_tab_bg_color=0x2d3234# insensitive_bg_color\n\n#\n# Adwaita theme\n#\n# copied from /usr/share/themes/Adwaita/gtk-2.0/gtkrc using: echo '' && \\\n#   cat /usr/share/themes/Adwaita/gtk-2.0/gtkrc | grep -e 'text_color\\\n#   \\|insensitive_bg_color\\|base_color\\|selected_bg_color\\|menu_color'\\\n#   | awk '{print $3}' | sed -e 's/\\\\n/\\'$'\\n''/g' | sed -e 's/\"//g'\\\n#   | sed -e 's/:#/ 0x/g' | sed -e 's/:black/ 0x000000/g' | sed -e\\\n#   's/:white/ 0xffffff/g' | awk '{$0 = sprintf(\"%30-s %s\", $1,$2);\\\n#   print}' | grep -v fg_color && echo ''\n#\n#ui_fg_color=0x000000# text_color slightly off-white to avoid whitereplace\n#ui_main_bg_color=0xf1f1f1# insensitive_bg_color\n#ui_text_bg_color=feffff# base_color slightly off-white to avoid whitereplace\n#ui_selection_color=0x4a90d9# selected_bg_color\n#ui_button_highlight_color=0xfeffff# menu_color slightly off-white to avoid whitereplace\n#ui_tab_active_bg_color=0xfeffff# base_color slightly off-white to avoid whitereplace\n#ui_tab_active_fg_color=0x000000# text_color\n#ui_tab_bg_color=0xf1f1f1# insensitive_bg_color\n\n#\n# Earthly theme:\n#\n#ui_fg_color=#100404\n#ui_main_bg_color=#c2a47b\n#ui_text_bg_color=#cdc9a5\n#ui_selection_color=#763024\n#ui_tab_active_bg_color=#af4b3f\n#ui_tab_active_fg_color=white\n#ui_tab_bg_color=#d2b48c\n\n#\n# Greenish theme:\n#\n#ui_fg_color=#100404\n#ui_main_bg_color=#c8d394\n#ui_text_bg_color=#bdd8b6\n#ui_selection_color=#7c5f42\n#ui_button_highlight_color=#adad70\n#ui_tab_active_bg_color=#b5b679\n#ui_tab_active_fg_color=#b60907\n#ui_tab_bg_color=#cac682\n\n#\n# Bluish theme:\n#\n#ui_fg_color=#100404\n#ui_main_bg_color=#99AABB\n#ui_text_bg_color=#BBCCDD\n#ui_selection_color=#99AABB\n#ui_button_highlight_color=#BBCCDD\n#ui_tab_active_bg_color=#BBCCDD\n#ui_tab_active_fg_color=#100404\n#ui_tab_bg_color=#AABBCC\n\n#\n# Default theme (Dillo+):\n#\nui_fg_color=#100404\nui_main_bg_color=#D5D8DD\nui_text_bg_color=#f6f5f4\nui_selection_color=#99AABB\nui_button_highlight_color=#FFFFFF\nui_tab_active_bg_color=#EEEEEE\nui_tab_active_fg_color=#100404\nui_tab_bg_color=#D5D8DD\n\n#\n# w00f theme\n#\n#ui_fg_color=black\n#ui_main_bg_color=#E8E8FF\n#ui_text_bg_color=#FFFFFF\n#ui_selection_color=#191970\n#ui_button_highlight_color=#a9a9a9\n#ui_tab_active_bg_color=#D3E7F3\n#ui_tab_active_fg_color=black\n#ui_tab_bg_color=#A4CCE3\n\n# Size of dillo panel\n# tiny   : buttons, location, and progress boxes in one row\n# small  : location in one row, buttons + progress boxes in another\n# medium : adds text labels to buttons and boxes\n# panel_size=tiny\n# panel_size=small\n#panel_size=medium\n\n#small_icons=NO\n\n# Here you can choose to hide some widgets of the dillo panel...\n#show_back=YES\n#show_forw=YES\n#show_home=YES\n#show_reload=YES\n#show_save=YES\n#show_stop=YES\n#show_bookmarks=YES\n#show_tools=YES\n#show_filemenu=YES\n#show_clear_url=YES\n#show_url=YES\n#show_search=YES\n#show_help=YES\n#show_progress_box=YES\n\n# Show tooltip popups for the UI\n#show_ui_tooltip=YES\n\n# Start dillo with the panels hidden?\n#fullwindow_start=NO\n\n# When filling out forms, our default behaviour is to submit on enterpress,\n# but only when there's a single text entry (to avoid incomplete submits).\n# OTOH, if you have to fill out the same form repeatedly, you may find it\n# useful to keep away from the mouse by forcing enter to submit.\n#enterpress_forces_submit=NO\n\n# A mouse's middle click over a link opens a new Tab.\n# If you prefer to open a new Window instead, set it to NO.\n#middle_click_opens_new_tab=YES\n\n# A mouse's middle click over a tab closes the Tab.\n# With mousewheel mouses, right click feels way better (set to YES).\n#right_click_closes_tab=NO\n\n# Mouse middle click by default drives drag-scrolling.\n# To paste an URL into the window instead of scrolling, set it to NO.\n# Note: You could always paste the URL onto the URL box clear button.\n#middle_click_drags_page=YES\n\n# Focus follows new Tabs.\n# You can hold SHIFT to temporarily revert this behaviour.\n#focus_new_tab=YES\n\n# Ask before quitting Dillo with more than one window or tab open.\n#show_quit_dialog=YES\n\n#-------------------------------------------------------------------------\n#                        DEBUG MESSAGES SECTION\n#-------------------------------------------------------------------------\n\n# Soon we should add the \"show_debug_messages=NO\" option...\n\n# Generic messages (mainly for debugging specific parts)\n# Change this to disable them.\n#show_msg=YES\n\n\n#-------------------------------------------------------------------------\n#                        HTML BUG MESSAGES SECTION\n#-------------------------------------------------------------------------\n\n# Accepted by the W3C validator but \"strongly discouraged\" by the SPEC.\n# (Such as \"TAB character inside <PRE>\").\n#show_extra_warnings=NO\n\n\n# -----------------------------------------------------------------------\n# dillorc ends here.\n"
  },
  {
    "path": "dist/Makefile",
    "content": "include ../Makefile.options\n\nall:\n\nclean:\n\ninstall:\n\t$(INSTALL_SH) -c -d \"$(APP_PATH)\"\n\t$(INSTALL_SH) -c -d \"$(ICON_PATH)\"\n\t$(INSTALL) -c -m 644 dillo-plus.desktop \"$(APP_PATH)\"\n\t$(INSTALL) -c -m 644 icons/dillo-plus.png \"$(ICON_PATH)\"\n\nuninstall:\n\trm -f \"$(APP_PATH)/dillo-plus.desktop\"\n\trm -f \"$(ICON_PATH)/dillo-plus.png\"\n"
  },
  {
    "path": "dist/dillo-plus.desktop",
    "content": "[Desktop Entry]\nGenericName=Web Browser\nGenericName[lt]=Web naršyklė\nName=Dillo Plus\nComment=Lightweight browser\nComment[lt]=Labai paprasta naršyklė\nMimeType=text/html;text/xml;application/xhtml+xml;\nExec=dillo-plus\nTerminal=false\nType=Application\nCategories=X-Internet-browser\nIcon=dillo-plus.png\n"
  },
  {
    "path": "dlib/Makefile",
    "content": "include ../Makefile.options\n\nlibDlib.a: dlib.o\n\t$(AR) $(ARFLAGS) libDlib.a dlib.o\n\t$(RANLIB) libDlib.a\n\ndlib.o: dlib.h dlib.c\n\t$(COMPILE) -c dlib.c\n\nall: libDlib.a\n\nclean:\n\trm -f *.o *.a\n\ninstall:\nuninstall:\n"
  },
  {
    "path": "dlib/dlib.c",
    "content": "/*\n * File: dlib.c\n *\n * Copyright (C) 2006-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/* Memory allocation, Simple dynamic strings, Lists (simple and sorted),\n * and a few utility functions\n */\n\n/*\n * TODO: vsnprintf() is in C99, maybe a simple replacement if necessary.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <ctype.h>\n\n#include \"dlib.h\"\n\nstatic bool_t dLib_show_msg = TRUE;\n\n/* dlib msgs go to stderr to avoid problems with filter dpis */\n#define DLIB_MSG(...)                              \\\n   D_STMT_START {                                  \\\n      if (dLib_show_msg)                           \\\n         fprintf(stderr, __VA_ARGS__);             \\\n   } D_STMT_END\n\n/*\n *- Memory --------------------------------------------------------------------\n */\n\nvoid *dMalloc (size_t size)\n{\n  void *value = malloc (size);\n  if (value == 0)\n     exit(1);\n  return value;\n}\n\nvoid *dRealloc (void *mem, size_t size)\n{\n  void *value = realloc (mem, size);\n  if (value == 0)\n     exit(1);\n  return value;\n}\n\nvoid *dMalloc0 (size_t size)\n{\n  void *value = dMalloc (size);\n  memset (value, 0, size);\n  return value;\n}\n\nvoid dFree (void *mem)\n{\n   free(mem);\n}\n\n/*\n *- strings (char *) ----------------------------------------------------------\n */\n\nchar *dStrdup(const char *s)\n{\n   if (s) {\n      int len = strlen(s)+1;\n      char *ns = dNew(char, len);\n      memcpy(ns, s, len);\n      return ns;\n   }\n   return NULL;\n}\n\nchar *dStrndup(const char *s, size_t sz)\n{\n   if (s) {\n      char *ns = dNew(char, sz+1);\n      memcpy(ns, s, sz);\n      ns[sz] = 0;\n      return ns;\n   }\n   return NULL;\n}\n\n/*\n * Concatenate a NULL-terminated list of strings\n */\nchar *dStrconcat(const char *s1, ...)\n{\n   va_list args;\n   char *s, *ns = NULL;\n\n   if (s1) {\n      Dstr *dstr = dStr_sized_new(64);\n      va_start(args, s1);\n      for (s = (char*)s1; s; s = va_arg(args, char*))\n         dStr_append(dstr, s);\n      va_end(args);\n      ns = dstr->str;\n      dStr_free(dstr, 0);\n   }\n   return ns;\n}\n\n/*\n * Remove leading and trailing whitespace\n */\nchar *dStrstrip(char *s)\n{\n   char *p;\n   int len;\n\n   if (s && *s) {\n      for (p = s; dIsspace(*p); ++p);\n      for (len = strlen(p); len && dIsspace(p[len-1]); --len);\n      if (p > s)\n         memmove(s, p, len);\n      s[len] = 0;\n   }\n   return s;\n}\n\n/*\n * Clear the contents of the string\n */\nvoid dStrshred(char *s)\n{\n   if (s)\n      memset(s, 0, strlen(s));\n}\n\n/*\n * Return a new string of length 'len' filled with 'c' characters\n */\nchar *dStrnfill(size_t len, char c)\n{\n   char *ret = dNew(char, len+1);\n   for (ret[len] = 0; len > 0; ret[--len] = c);\n   return ret;\n}\n\n/*\n * strsep() implementation\n */\nchar *dStrsep(char **orig, const char *delim)\n{\n   char *str, *p;\n\n   if (!(str = *orig))\n      return NULL;\n\n   p = strpbrk(str, delim);\n   if (p) {\n      *p++ = 0;\n      *orig = p;\n   } else {\n      *orig = NULL;\n   }\n   return str;\n}\n\n/*\n * ASCII functions to avoid the case difficulties introduced by I/i in\n * Turkic locales.\n */\n\n/*\n * Case insensitive strstr\n */\nchar *dStriAsciiStr(const char *haystack, const char *needle)\n{\n   int i, j;\n   char *ret = NULL;\n\n   if (haystack && needle) {\n      for (i = 0, j = 0; haystack[i] && needle[j]; ++i)\n         if (D_ASCII_TOLOWER(haystack[i]) == D_ASCII_TOLOWER(needle[j])) {\n            ++j;\n         } else if (j) {\n            i -= j;\n            j = 0;\n         }\n      if (!needle[j])                 /* Got all */\n         ret = (char *)(haystack + i - j);\n   }\n   return ret;\n}\n\nint dStrAsciiCasecmp(const char *s1, const char *s2)\n{\n   int ret = 0;\n\n   while ((*s1 || *s2) &&\n          !(ret = D_ASCII_TOLOWER(*s1) - D_ASCII_TOLOWER(*s2))) {\n      s1++;\n      s2++;\n   }\n   return ret;\n}\n\nint dStrnAsciiCasecmp(const char *s1, const char *s2, size_t n)\n{\n   int ret = 0;\n\n   while (n-- && (*s1 || *s2) &&\n          !(ret = D_ASCII_TOLOWER(*s1) - D_ASCII_TOLOWER(*s2))) {\n      s1++;\n      s2++;\n   }\n   return ret;\n}\n\n/*\n *- dStr ----------------------------------------------------------------------\n */\n\n/*\n * Private allocator\n */\nstatic void dStr_resize(Dstr *ds, int n_sz, int keep)\n{\n   if (n_sz >= 0) {\n      if (keep && n_sz > ds->len) {\n         ds->str = (Dstr_char_t*) dRealloc (ds->str, n_sz*sizeof(Dstr_char_t));\n         ds->sz = n_sz;\n      } else {\n         dFree(ds->str);\n         ds->str = dNew(Dstr_char_t, n_sz);\n         ds->sz = n_sz;\n         ds->len = 0;\n         ds->str[0] = 0;\n      }\n   }\n}\n\n/*\n * Create a new string with a given size.\n * Initialized to \"\"\n */\nDstr *dStr_sized_new (int sz)\n{\n   Dstr *ds;\n   if (sz < 2)\n      sz = 2;\n\n   ds = dNew(Dstr, 1);\n   ds->str = NULL;\n   dStr_resize(ds, sz + 1, 0); /* (sz + 1) for the extra '\\0' */\n   return ds;\n}\n\n/*\n * Return memory if there's too much allocated\n */\nvoid dStr_fit (Dstr *ds)\n{\n   dStr_resize(ds, ds->len + 1, 1);\n}\n\n/*\n * Insert a C string, at a given position, into a Dstr (providing length).\n * Note: It also works with embedded nil characters.\n */\nvoid dStr_insert_l (Dstr *ds, int pos_0, const char *s, int l)\n{\n   int n_sz;\n\n   if (ds && s && l && pos_0 >= 0 && pos_0 <= ds->len) {\n      for (n_sz = ds->sz; ds->len + l >= n_sz; n_sz *= 2);\n      if (n_sz > ds->sz) {\n         dStr_resize(ds, n_sz, (ds->len > 0) ? 1 : 0);\n      }\n      if (pos_0 < ds->len)\n         memmove(ds->str+pos_0+l, ds->str+pos_0, ds->len-pos_0);\n      memcpy(ds->str+pos_0, s, l);\n      ds->len += l;\n      ds->str[ds->len] = 0;\n   }\n}\n\n/*\n * Insert a C string, at a given position, into a Dstr\n */\nvoid dStr_insert (Dstr *ds, int pos_0, const char *s)\n{\n   if (s)\n      dStr_insert_l(ds, pos_0, s, strlen(s));\n}\n\n/*\n * Append a C string to a Dstr (providing length).\n * Note: It also works with embedded nil characters.\n */\nvoid dStr_append_l (Dstr *ds, const char *s, int l)\n{\n   dStr_insert_l(ds, ds->len, s, l);\n}\n\n/*\n * Append a C string to a Dstr.\n */\nvoid dStr_append (Dstr *ds, const char *s)\n{\n   dStr_append_l(ds, s, strlen(s));\n}\n\n/*\n * Create a new string.\n * Initialized to 's' or empty if 's == NULL'\n */\nDstr *dStr_new (const char *s)\n{\n   Dstr *ds = dStr_sized_new(0);\n   if (s && *s)\n      dStr_append(ds, s);\n   return ds;\n}\n\n/*\n * Free a dillo string.\n * if 'all' free everything, else free the structure only.\n */\nvoid dStr_free (Dstr *ds, int all)\n{\n   if (ds) {\n      if (all)\n         dFree(ds->str);\n      dFree(ds);\n   }\n}\n\n/*\n * Append one character.\n */\nvoid dStr_append_c (Dstr *ds, int c)\n{\n   char cs[2];\n\n   if (ds) {\n      if (ds->sz > ds->len + 1) {\n         ds->str[ds->len++] = (Dstr_char_t)c;\n         ds->str[ds->len] = 0;\n      } else {\n         cs[0] = (Dstr_char_t)c;\n         cs[1] = 0;\n         dStr_append_l (ds, cs, 1);\n      }\n   }\n}\n\n/*\n * Truncate a Dstr to be 'len' bytes long.\n */\nvoid dStr_truncate (Dstr *ds, int len)\n{\n   if (ds && len < ds->len) {\n      ds->str[len] = 0;\n      ds->len = len;\n   }\n}\n\n/*\n * Clear a Dstr.\n */\nvoid dStr_shred (Dstr *ds)\n{\n   if (ds && ds->sz > 0)\n      memset(ds->str, '\\0', ds->sz);\n}\n\n/*\n * Erase a substring.\n */\nvoid dStr_erase (Dstr *ds, int pos_0, int len)\n{\n   if (ds && pos_0 >= 0 && len > 0 && pos_0 + len <= ds->len) {\n      memmove(ds->str + pos_0, ds->str + pos_0 + len, ds->len - pos_0 - len);\n      ds->len -= len;\n      ds->str[ds->len] = 0;\n   }\n}\n\n/*\n * vsprintf-like function that appends.\n * Used by: dStr_vsprintf(), dStr_sprintf() and dStr_sprintfa().\n */\nvoid dStr_vsprintfa (Dstr *ds, const char *format, va_list argp)\n{\n   int n, n_sz;\n\n   if (ds && format) {\n      va_list argp2;         /* Needed in case of looping on non-32bit arch */\n      while (1) {\n         va_copy(argp2, argp);\n         n = vsnprintf(ds->str + ds->len, ds->sz - ds->len, format, argp2);\n         va_end(argp2);\n#if defined(__sgi)\n         /* IRIX does not conform to C99; if the entire argument did not fit\n          * into the buffer, n = buffer space used (minus 1 for terminator)\n          */\n         if (n > -1 && n + 1 < ds->sz - ds->len) {\n            ds->len += n;      /* Success! */\n            break;\n         } else {\n            n_sz = ds->sz * 2;\n         }\n#else\n         if (n > -1 && n < ds->sz - ds->len) {\n            ds->len += n;      /* Success! */\n            break;\n         } else if (n > -1) {  /* glibc >= 2.1 */\n            n_sz = ds->len + n + 1;\n         } else {              /* old glibc */\n            n_sz = ds->sz * 2;\n         }\n#endif\n         dStr_resize(ds, n_sz, (ds->len > 0) ? 1 : 0);\n      }\n   }\n}\n\n/*\n * vsprintf-like function.\n */\nvoid dStr_vsprintf (Dstr *ds, const char *format, va_list argp)\n{\n   if (ds) {\n      dStr_truncate(ds, 0);\n      dStr_vsprintfa(ds, format, argp);\n   }\n}\n\n/*\n * Printf-like function\n */\nvoid dStr_sprintf (Dstr *ds, const char *format, ...)\n{\n   va_list argp;\n\n   if (ds && format) {\n      va_start(argp, format);\n      dStr_vsprintf(ds, format, argp);\n      va_end(argp);\n   }\n}\n\n/*\n * Printf-like function that appends.\n */\nvoid dStr_sprintfa (Dstr *ds, const char *format, ...)\n{\n   va_list argp;\n\n   if (ds && format) {\n      va_start(argp, format);\n      dStr_vsprintfa(ds, format, argp);\n      va_end(argp);\n   }\n}\n\n/*\n * Compare two dStrs.\n */\nint dStr_cmp(Dstr *ds1, Dstr *ds2)\n{\n   int ret = 0;\n\n   if (ds1 && ds2)\n      ret = memcmp(ds1->str, ds2->str, MIN(ds1->len+1, ds2->len+1));\n   return ret;\n}\n\n/*\n * Return a pointer to the first occurrence of needle in haystack.\n */\nchar *dStr_memmem(Dstr *haystack, Dstr *needle)\n{\n   int i;\n\n   if (needle && haystack) {\n      if (needle->len == 0)\n         return haystack->str;\n\n      for (i = 0; i <= (haystack->len - needle->len); i++) {\n         if (haystack->str[i] == needle->str[0] &&\n             !memcmp(haystack->str + i, needle->str, needle->len))\n            return haystack->str + i;\n      }\n   }\n   return NULL;\n}\n\n/*\n * Return a printable representation of the provided Dstr, limited to a length\n * of roughly maxlen.\n *\n * This is NOT threadsafe.\n */\nconst char *dStr_printable(Dstr *in, int maxlen)\n{\n   int i;\n   static const char *const HEX = \"0123456789ABCDEF\";\n   static Dstr *out = NULL;\n\n   if (in == NULL)\n      return NULL;\n\n   if (out)\n      dStr_truncate(out, 0);\n   else\n      out = dStr_sized_new(in->len);\n\n   for (i = 0; (i < in->len) && (out->len < maxlen); ++i) {\n      if (isprint(in->str[i]) || (in->str[i] == '\\n')) {\n         dStr_append_c(out, in->str[i]);\n      } else {\n         dStr_append_l(out, \"\\\\x\", 2);\n         dStr_append_c(out, HEX[(in->str[i] >> 4) & 15]);\n         dStr_append_c(out, HEX[in->str[i] & 15]);\n      }\n   }\n   if (out->len >= maxlen)\n      dStr_append(out, \"...\");\n   return out->str;\n}\n\n/*\n *- dList ---------------------------------------------------------------------\n */\n\n/*\n * Create a new empty list\n */\nDlist *dList_new(int size)\n{\n   Dlist *l;\n   if (size <= 0)\n      return NULL;\n\n   l = dNew(Dlist, 1);\n   l->len = 0;\n   l->sz = size;\n   l->list = dNew(void*, l->sz);\n   return l;\n}\n\n/*\n * Free a list (not its elements)\n */\nvoid dList_free (Dlist *lp)\n{\n   if (!lp)\n      return;\n\n   dFree(lp->list);\n   dFree(lp);\n}\n\n/*\n * Insert an element at a given position [0 based]\n */\nvoid dList_insert_pos (Dlist *lp, void *data, int pos0)\n{\n   int i;\n\n   if (!lp || pos0 < 0 || pos0 > lp->len)\n      return;\n\n   if (lp->sz == lp->len) {\n      lp->sz *= 2;\n      lp->list = (void**) dRealloc (lp->list, lp->sz*sizeof(void*));\n   }\n   ++lp->len;\n\n   for (i = lp->len - 1; i > pos0; --i)\n      lp->list[i] = lp->list[i - 1];\n   lp->list[pos0] = data;\n}\n\n/*\n * Append a data item to the list\n */\nvoid dList_append (Dlist *lp, void *data)\n{\n   dList_insert_pos(lp, data, lp->len);\n}\n\n/*\n * Prepend a data item to the list\n */\nvoid dList_prepend (Dlist *lp, void *data)\n{\n   dList_insert_pos(lp, data, 0);\n}\n\n/*\n * For completing the ADT.\n */\nint dList_length (Dlist *lp)\n{\n   if (!lp)\n      return 0;\n   return lp->len;\n}\n\n/*\n * Remove a data item without preserving order.\n */\nvoid dList_remove_fast (Dlist *lp, const void *data)\n{\n   int i;\n\n   if (!lp)\n      return;\n\n   for (i = 0; i < lp->len; ++i) {\n      if (lp->list[i] == data) {\n         lp->list[i] = lp->list[--lp->len];\n         break;\n      }\n   }\n}\n\n/*\n * Remove a data item preserving order.\n */\nvoid dList_remove (Dlist *lp, const void *data)\n{\n   int i, j;\n\n   if (!lp)\n      return;\n\n   for (i = 0; i < lp->len; ++i) {\n      if (lp->list[i] == data) {\n         --lp->len;\n         for (j = i; j < lp->len; ++j)\n            lp->list[j] = lp->list[j + 1];\n         break;\n      }\n   }\n}\n\n/*\n * Return the nth data item,\n * NULL when not found or 'n0' is out of range\n */\nvoid *dList_nth_data (Dlist *lp, int n0)\n{\n   if (!lp || n0 < 0 || n0 >= lp->len)\n      return NULL;\n   return lp->list[n0];\n}\n\n/*\n * Return the found data item, or NULL if not present.\n */\nvoid *dList_find (Dlist *lp, const void *data)\n{\n   int i = dList_find_idx(lp, data);\n   return (i >= 0) ? lp->list[i] : NULL;\n}\n\n/*\n * Search a data item.\n * Return value: its index if found, -1 if not present.\n * (this is useful for a list of integers, for finding number zero).\n */\nint dList_find_idx (Dlist *lp, const void *data)\n{\n   int i, ret = -1;\n\n   if (!lp)\n      return ret;\n\n   for (i = 0; i < lp->len; ++i) {\n      if (lp->list[i] == data) {\n         ret = i;\n         break;\n      }\n   }\n   return ret;\n}\n\n/*\n * Search a data item using a custom function.\n * func() is given the list item and the user data as parameters.\n * Return: data item when found, NULL otherwise.\n */\nvoid *dList_find_custom (Dlist *lp, const void *data, dCompareFunc func)\n{\n   int i;\n   void *ret = NULL;\n\n   if (!lp)\n      return ret;\n\n   for (i = 0; i < lp->len; ++i) {\n      if (func(lp->list[i], data) == 0) {\n         ret = lp->list[i];\n         break;\n      }\n   }\n   return ret;\n}\n\n/*\n * QuickSort implementation.\n * This allows for a simple compare function for all the ADT.\n */\nstatic void QuickSort(void **left, void **right, dCompareFunc compare)\n{\n  void **p = left, **q = right, **t = left;\n\n  while (1) {\n     while (p != t && compare(*p, *t) < 0)\n        ++p;\n     while (q != t && compare(*q, *t) > 0)\n        --q;\n     if (p > q)\n        break;\n     if (p < q) {\n        void *tmp = *p;\n        *p = *q;\n        *q = tmp;\n        if (t == p)\n           t = q;\n        else if (t == q)\n           t = p;\n     }\n     if (++p > --q)\n        break;\n  }\n\n  if (left < q)\n     QuickSort(left, q, compare);\n  if (p < right)\n     QuickSort(p, right, compare);\n}\n\n/*\n * Sort the list using a custom function\n */\nvoid dList_sort (Dlist *lp, dCompareFunc func)\n{\n   if (lp && lp->len > 1) {\n      QuickSort(lp->list, lp->list + lp->len - 1, func);\n   }\n}\n\n/*\n * Insert an element into a sorted list.\n * The comparison function receives two list elements.\n */\nvoid dList_insert_sorted (Dlist *lp, void *data, dCompareFunc func)\n{\n   int i, st, min, max;\n\n   if (lp) {\n      min = st = i = 0;\n      max = lp->len - 1;\n      while (min <= max) {\n         i = (min + max) / 2;\n         st = func(lp->list[i], data);\n         if (st < 0) {\n            min = i + 1;\n         } else if (st > 0) {\n            max = i - 1;\n         } else {\n            break;\n         }\n      }\n      dList_insert_pos(lp, data, (st >= 0) ? i : i+1);\n   }\n}\n\n/*\n * Search a sorted list.\n * Return the found data item, or NULL if not present.\n * func() is given the list item and the user data as parameters.\n */\nvoid *dList_find_sorted (Dlist *lp, const void *data, dCompareFunc func)\n{\n   int i, st, min, max;\n   void *ret = NULL;\n\n   if (lp && lp->len) {\n      min = 0;\n      max = lp->len - 1;\n      while (min <= max) {\n         i = (min + max) / 2;\n         st = func(lp->list[i], data);\n         if (st < 0) {\n            min = i + 1;\n         } else if (st > 0) {\n            max = i - 1;\n         } else {\n            ret = lp->list[i];\n            break;\n         }\n      }\n   }\n\n   return ret;\n}\n\n/*\n *- Parse function ------------------------------------------------------------\n */\n\n/*\n * Take a dillo rc line and return 'name' and 'value' pointers to it.\n * Notes:\n *    - line is modified!\n *    - it skips blank lines and lines starting with '#'\n *\n * Return value: 1 on blank line or comment, 0 on successful value/pair,\n *              -1 otherwise.\n */\nint dParser_parse_rc_line(char **line, char **name, char **value)\n{\n   char *eq, *p;\n   int len, ret = -1;\n\n   dReturn_val_if_fail(*line, ret);\n\n   *name = NULL;\n   *value = NULL;\n   dStrstrip(*line);\n   if (!*line[0] || *line[0] == '#') {\n      /* blank line or comment */\n      ret = 1;\n   } else if ((eq = strchr(*line, '='))) {\n      /* get name */\n      for (p = *line; *p && *p != '=' && !dIsspace(*p); ++p);\n      *p = 0;\n      *name = *line;\n\n      /* skip whitespace */\n      if (p < eq)\n         for (++p; dIsspace(*p); ++p);\n\n      /* get value */\n      if (p == eq) {\n         for (++p; dIsspace(*p); ++p);\n         len = strlen(p);\n         if (len >= 2 && *p == '\"' && p[len-1] == '\"') {\n            p[len-1] = 0;\n            ++p;\n         }\n         *value = p;\n         ret = 0;\n      }\n   }\n\n   return ret;\n}\n\n/*\n *- Dlib messages -------------------------------------------------------------\n */\nvoid dLib_show_messages(bool_t show)\n{\n   dLib_show_msg = show;\n}\n\n/*\n *- Misc utility functions ----------------------------------------------------\n */\n\n/*\n * Return the current working directory in a new string\n */\nchar *dGetcwd ()\n{\n  size_t size = 128;\n\n  while (1) {\n      char *buffer = dNew(char, size);\n      if (getcwd (buffer, size) == buffer)\n        return buffer;\n      dFree (buffer);\n      if (errno != ERANGE)\n        return 0;\n      size *= 2;\n  }\n}\n\n/*\n * Return the home directory in a static string (don't free)\n */\nchar *dGethomedir ()\n{\n   static char *homedir = NULL;\n\n   if (!homedir) {\n      if (getenv(\"HOME\")) {\n         homedir = dStrdup(getenv(\"HOME\"));\n\n      } else if (getenv(\"HOMEDRIVE\") && getenv(\"HOMEPATH\")) {\n         homedir = dStrconcat(getenv(\"HOMEDRIVE\"), getenv(\"HOMEPATH\"), NULL);\n      } else {\n         DLIB_MSG(\"dGethomedir: $HOME not set, using '/'.\\n\");\n         homedir = dStrdup(\"/\");\n      }\n   }\n   return homedir;\n}\n\n/*\n * Get a line from a FILE stream.\n * Return value: read line on success, NULL on EOF.\n */\nchar *dGetline (FILE *stream)\n{\n   int ch;\n   Dstr *dstr;\n   char *line;\n\n   dReturn_val_if_fail (stream, 0);\n\n   dstr = dStr_sized_new(64);\n   while ((ch = fgetc(stream)) != EOF) {\n      dStr_append_c(dstr, ch);\n      if (ch == '\\n')\n         break;\n   }\n\n   line = (dstr->len) ? dstr->str : NULL;\n   dStr_free(dstr, (line) ? 0 : 1);\n   return line;\n}\n\n/*\n * Close a FD handling EINTR.\n */\nint dClose(int fd)\n{\n   int st;\n\n   do\n      st = close(fd);\n   while (st == -1 && errno == EINTR);\n   return st;\n}\n"
  },
  {
    "path": "dlib/dlib.h",
    "content": "#ifndef __DLIB_H__\n#define __DLIB_H__\n\n#include <stdio.h>     /* for FILE*  */\n#include <stddef.h>    /* for size_t */\n#include <stdarg.h>    /* for va_list */\n#include <string.h>    /* for strerror */\n\n#include \"d_size.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n *-- Common macros -----------------------------------------------------------\n */\n#ifndef FALSE\n#define FALSE   (0)\n#endif\n\n#ifndef TRUE\n#define TRUE    (!FALSE)\n#endif\n\n#undef  MAX\n#define MAX(a, b)  (((a) > (b)) ? (a) : (b))\n\n#undef  MIN\n#define MIN(a, b)  (((a) < (b)) ? (a) : (b))\n\n/* Handle signed char */\n#define dIsspace(c) isspace((uchar_t)(c))\n#define dIsalnum(c) isalnum((uchar_t)(c))\n\n#define D_ASCII_TOUPPER(c) (((c) >= 'a' && (c) <= 'z') ? (c) - 0x20 : (c))\n#define D_ASCII_TOLOWER(c) (((c) >= 'A' && (c) <= 'Z') ? (c) + 0x20 : (c))\n/*\n *-- Casts -------------------------------------------------------------------\n */\n/* TODO: include a void* size test in configure.in */\n/* (long) works for both 32bit and 64bit */\n#define VOIDP2INT(p)    ((long)(p))\n#define INT2VOIDP(i)    ((void*)((long)(i)))\n\n/*\n *-- Memory -------------------------------------------------------------------\n */\n#define dNew(type, count) \\\n   ((type *) dMalloc ((unsigned) sizeof (type) * (count)))\n#define dNew0(type, count) \\\n   ((type *) dMalloc0 ((unsigned) sizeof (type) * (count)))\n\nvoid *dMalloc (size_t size);\nvoid *dRealloc (void *mem, size_t size);\nvoid *dMalloc0 (size_t size);\nvoid dFree (void *mem);\n\n/*\n *- Debug macros --------------------------------------------------------------\n */\n#define D_STMT_START      do\n#define D_STMT_END        while (0)\n#define dReturn_if(expr)               \\\n   D_STMT_START{                       \\\n      if (expr) { return; };           \\\n   }D_STMT_END\n#define dReturn_val_if(expr,val)       \\\n   D_STMT_START{                       \\\n      if (expr) { return val; };       \\\n   }D_STMT_END\n#define dReturn_if_fail(expr)          \\\n   D_STMT_START{                       \\\n      if (!(expr)) { return; };        \\\n   }D_STMT_END\n#define dReturn_val_if_fail(expr,val)  \\\n   D_STMT_START{                       \\\n      if (!(expr)) { return val; };    \\\n   }D_STMT_END\n\n/*\n *- C strings -----------------------------------------------------------------\n */\nchar *dStrdup(const char *s);\nchar *dStrndup(const char *s, size_t sz);\nchar *dStrconcat(const char *s1, ...);\nchar *dStrstrip(char *s);\nchar *dStrnfill(size_t len, char c);\nchar *dStrsep(char **orig, const char *delim);\nvoid dStrshred(char *s);\nchar *dStriAsciiStr(const char *haystack, const char *needle);\nint dStrAsciiCasecmp(const char *s1, const char *s2);\nint dStrnAsciiCasecmp(const char *s1, const char *s2, size_t n);\n\n#define dStrerror strerror\n\n/*\n *-- dStr ---------------------------------------------------------------------\n */\n#define Dstr_char_t    char\n\ntypedef struct {\n   int sz;          /* allocated size (private) */\n   int len;\n   Dstr_char_t *str;\n} Dstr;\n\nDstr *dStr_new (const char *s);\nDstr *dStr_sized_new (int sz);\nvoid dStr_fit (Dstr *ds);\nvoid dStr_free (Dstr *ds, int all);\nvoid dStr_append_c (Dstr *ds, int c);\nvoid dStr_append (Dstr *ds, const char *s);\nvoid dStr_append_l (Dstr *ds, const char *s, int l);\nvoid dStr_insert (Dstr *ds, int pos_0, const char *s);\nvoid dStr_insert_l (Dstr *ds, int pos_0, const char *s, int l);\nvoid dStr_truncate (Dstr *ds, int len);\nvoid dStr_shred (Dstr *ds);\nvoid dStr_erase (Dstr *ds, int pos_0, int len);\nvoid dStr_vsprintfa (Dstr *ds, const char *format, va_list argp);\nvoid dStr_vsprintf (Dstr *ds, const char *format, va_list argp);\nvoid dStr_sprintf (Dstr *ds, const char *format, ...);\nvoid dStr_sprintfa (Dstr *ds, const char *format, ...);\nint  dStr_cmp(Dstr *ds1, Dstr *ds2);\nchar *dStr_memmem(Dstr *haystack, Dstr *needle);\nconst char *dStr_printable(Dstr *in, int maxlen);\n\n/*\n *-- dList --------------------------------------------------------------------\n */\ntypedef struct {\n   int sz;          /* allocated size (private) */\n   int len;\n   void **list;\n} Dlist;\n\n/* dCompareFunc:\n * Return: 0 if parameters are equal (for dList_find_custom).\n * Return: 0 if equal,  < 0 if (a < b),  > 0 if (b < a) --for insert sorted.\n *\n * For finding a data node with an external key, the comparison function\n * parameters are: first the data node, and then the key.\n */\ntypedef int (*dCompareFunc) (const void *a, const void *b);\n\n\nDlist *dList_new(int size);\nvoid dList_free (Dlist *lp);\nvoid dList_append (Dlist *lp, void *data);\nvoid dList_prepend (Dlist *lp, void *data);\nvoid dList_insert_pos (Dlist *lp, void *data, int pos0);\nint  dList_length (Dlist *lp);\nvoid dList_remove (Dlist *lp, const void *data);\nvoid dList_remove_fast (Dlist *lp, const void *data);\nvoid *dList_nth_data (Dlist *lp, int n0);\nvoid *dList_find (Dlist *lp, const void *data);\nint  dList_find_idx (Dlist *lp, const void *data);\nvoid *dList_find_custom (Dlist *lp, const void *data, dCompareFunc func);\nvoid dList_sort (Dlist *lp, dCompareFunc func);\nvoid dList_insert_sorted (Dlist *lp, void *data, dCompareFunc func);\nvoid *dList_find_sorted (Dlist *lp, const void *data, dCompareFunc func);\n\n/*\n *- Parse function ------------------------------------------------------------\n */\nint dParser_parse_rc_line(char **line, char **name, char **value);\n\n/*\n *- Dlib messages -------------------------------------------------------------\n */\nvoid dLib_show_messages(bool_t show);\n\n/*\n *- Misc utility functions ----------------------------------------------------\n */\nchar *dGetcwd();\nchar *dGethomedir();\nchar *dGetline(FILE *stream);\nint dClose(int fd);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __DLIB_H__ */\n\n"
  },
  {
    "path": "doc/Cookies.txt",
    "content": "Jan 2002, Jrgen Viksell - jorgen.viksell@telia.com,\n          Jorge Arellano Cid --\nLast update: March 2010\n\n\n==================\n Cookies in Dillo\n==================\n\nThe current specification for cookies is RFC 6265\n( http://tools.ietf.org/html/rfc6265 ).\n\nCookies are handled by a dpi (plugin) which shares them between your\ninstances of Dillo.\n\nCurrent cookie limits are: 20 per domain, and 1200 in total.\n\nWhen the dpi exits, cookies that you have ACCEPTed are saved to\n~/.dillo/cookies.txt, and ACCEPT_SESSION cookies are forgotten.\nThe dpi normally exits after a period of inactivity, but you can force it to\nexit with the command \"dpidc stop\".\n\n\n=====================\n Controlling cookies\n=====================\n\nOut of the box, dillo rejects all cookies.\n\n\nIf you want to accept certain cookies, you can specify rules for different\ndomains in the file ~/.dillo/cookiesrc. The syntax looks like:\n\n#host         action\nDEFAULT       DENY\nfltk.org      ACCEPT\n.host.com     ACCEPT_SESSION\n\nLine 0: Comment line begins with '#'.\nLine 1: Deny all cookies from all domains not otherwise specified.\nLine 2: Accept all cookies from fltk.org, and save them to\n        ~/.dillo/cookies.txt when the cookies dpi exits.\nLine 3: Accept all cookies from all subdomains of host.com, but\n        do not save them when the dpi exits.\n\n\nIf you are positive that you will never want any cookies, you can\nconfigure/compile Dillo without cookie support. The option is:\n./configure --disable-cookies\n\n\n===================\n Cookies & Privacy\n===================\n\n Cookies can be a severe threat to personal privacy. The pages you\nvisit can be tracked, logged, and associated to a peronal data-record,\nallowing the possibility of building a detailed profile of your\nbrowsing habits.\n\n This data is sold to companies that profit from direct use of such\ninformation (SPAM, Spying, etc).\n\n If this data is cross-referenced with other databases, they can end up\nwith more information than you have about yourself.\n\n Some people may tell you this is \"paranoid\". But please, take my words\nas those of someone who has written a web browser, a cookies implementation,\nand who has deep understanding of HTTP and cookies.\n\n The dillo project is especially concerned about privacy and security\nissues. Our advice is to avoid cookies whenever possible and at most set\nACCEPT_SESSION to specific, trusted sites.  -- You have been warned.\n\n"
  },
  {
    "path": "doc/Makefile",
    "content": "include ../Makefile.options\n\nall: manpage.1.in\n\tsed \"s/%BINNAME%/$(BINNAME)/g\" manpage.1.in > $(BINNAME).1\n\nclean:\n\trm -f $(BINNAME).1\n\ninstall: all\n\t$(INSTALL_SH) -c -d \"$(DOC_PATH)\"\n\t$(INSTALL) -c -m 644 user_help.html \"$(DOC_PATH)\"\n\t$(INSTALL) -c -m 644 Cookies.txt \"$(DOC_PATH)\"\n\t$(INSTALL_SH) -c -d \"$(MAN_PATH)\"\n\t$(INSTALL) -c -m 644 $(BINNAME).1 \"$(MAN_PATH)\"\n\nuninstall:\n\trm -f \"$(DOC_PATH)/*.html\"\n\trm -f \"$(DOC_PATH)/*.txt\"\n\trm -f \"$(MAN_PATH)/$(BINNAME).1\"\n"
  },
  {
    "path": "doc/README",
    "content": "Last update: June 2015\n\nThis directory contains user documentation. Developer documentation is\nonly stored in the Hg repository at <http://hg.dillo.org/dillo/>, in\nthe directory \"devdoc\", but not part of the tarball.\n"
  },
  {
    "path": "doc/manpage.1.in",
    "content": ".TH dillo 1 \"May 28, 2015\" \"\" \"USER COMMANDS\"\n.SH NAME\n%BINNAME% \\- web browser\n.SH SYNOPSIS\n.B %BINNAME%\n.RI [ OPTION ]...\n.RB [ \\-\\- ]\n.RI [ URL | FILE ]...\n.SH DESCRIPTION\n.PP\nDillo is a lightweight graphical web browser that aims to be secure.\nIt handles HTTP internally, and FILE, FTP, and\nDATA URIs are handled through a plugin system (dpi). In addition,\n.I EXPERIMENTAL\nHTTPS support can be enabled. Both FTP and Dillo's download manager use the\n.BR wget (1)\ndownloader.\n.PP\nDillo displays HTML, text, PNG, JPEG, and GIF files.\nIt handles cookies, HTTP authentication (basic and digest), proxying (basic),\nand some CSS.\n.PP\nFramesets are displayed as links to frames, and there is currently\nno support for javascript or video.\n.PP\nIn order to use the hyphenation feature, pattern files from CTAN need to\nbe installed to\n.IR /usr/local/lib/%BINNAME%/hyphenation/ .\nThis can be done with the script\n.IR dillo-install-hyphenation .\nCall it with ISO-639-1 language codes as arguments, or without arguments\nto get more help.\n.SH OPTIONS\n.TP\n\\fB\\-f\\fR, \\fB\\-\\-fullwindow\\fR\nStart in full window mode: hide address bar, navigation buttons, menu, and\nstatus bar.\n.TP\n\\fB\\-g\\fR, \\fB\\-geometry \\fIGEO\\fR\nSet initial window position where \\fIGEO\\fR is\n\\fIW\\fBx\\fIH\\fR[{\\fB+\\-\\fR}\\fIX\\fR{\\fB+\\-\\fR}\\fIY\\fR].\n.TP\n\\fB\\-h\\fR, \\fB\\-\\-help\\fR\nDisplay this help text and exit.\n.TP\n\\fB\\-l\\fR, \\fB\\-\\-local\\fR\nDon't load images or stylesheets, or follow redirections, for these FILEs or\nURLs. This is intended for use with HTML email.\n.TP\n\\fB\\-v\\fR, \\fB\\-\\-version\\fR\nDisplay version info and exit.\n.TP\n\\fB\\-x\\fR, \\fB\\-\\-xid \\fIXID\\fR\nOpen first Dillo window in an existing window whose window ID is \\fIXID\\fR.\n.SH EXIT STATUS\n.TP\n.B 0\nNo error.\n.TP\n.B 1\nInternal error.\n.TP\n.B 2\nError in command line arguments.\n.SH ENVIRONMENT\n.TP\n.BR \"HOME \" \"(or \" \"HOMEDRIVE \" \"and \" \"HOMEPATH \" \"on Cygwin)\"\nUser's home directory.\n.TP\n.B http_proxy\nURL of proxy to send HTTP/HTTPS traffic through.\n.SH FILES\n.TP\n.I dpid-plus\nDillo plugin daemon\n.TP\n.I dpidc-plus\nControl program for dpid-plus.\n.TP\n.I ~/.%BINNAME%/bm.txt\nUser bookmarks\n.TP\n.I ~/.%BINNAME%/certs/\nSaved certificates for HTTPS.\n.TP\n.I ~/.%BINNAME%/cookies.txt\nStored cookies\n.TP\n.I ~/.%BINNAME%/cookiesrc\nCookie settings\n.TP\n.I ~/.%BINNAME%/dillorc\nConfiguration file.\n.TP\n.I ~/.%BINNAME%/domainrc\nRules for cross-domain requests.\n.TP\n.I ~/.%BINNAME%/dpid_comm_keys\nKeys used in dpi daemon communication.\n.TP\n.I ~/.%BINNAME%/dpidrc\nContains name of directory containing dpis, and associates\ndpi files with protocols.\n.TP\n.I ~/.%BINNAME%/keysrc\nKeybindings.\n.TP\n.I ~/.%BINNAME%/style.css\nUser style sheet.\n.TP\n.I /usr/local/lib/%BINNAME%/hyphenation/\nHyphenation pattern files.\n.SH SEE ALSO\n.BR wget (1)\n.PP\nDillo website:\n.B https://dillo-browser.github.io/\nDillo-plus website:\n.B https://github.com/crossbowerbt/dillo-plus/\n"
  },
  {
    "path": "doc/user_help.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n<html>\n\n<head>\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n  <title> Dillo Web Browser Help</title>\n\t<style>\n\t        body {\n\t\t\tbackground-color: #778899;\n\t\t\tcolor: #0f0f0f;\n\t\t}\n\t        h1 {\n\t\t\tcolor: white;\n\t\t\tmargin: 0 1em 0 1em;\n\t\t\ttext-align: center;\n\t\t}\n\t\tul#menu {\n\t\t\tpadding: 0;\n\t\t\tmargin: 0 1em 0 1em;\n\t\t\tlist-style-type: none;\n\t\t}\n\t\tth {\n\t\t    color: white;\n\t\t    background-color: #4F4F4F;\n\t\t}\n\t\ttd.content {\n\t\t    background-color: #FAFAFA;\n\t\t}\n\t</style>\n</head>\n\n<body>\n\n<br/>\n\n<table width='100%' border='0' cellspacing='1' cellpadding='3' bgcolor='#000000'>\n  <tr><th><h1>Dillo+ 3.3.0 Help</h1></th></tr>\n</table>\n\n<br/>\n\n<table style=\"margin: 0;\">\n\t<tr>\n\t\t<td>\n\t\t\t<table border='0' cellspacing='1' cellpadding='3' bgcolor='#000000'>\n\t\t\t\t<tr><th>Menu</th></tr>\n\t\t\t\t<tr><td class=\"content\">\n\t\t\t\t\t\t<ul id='menu'>\n\t\t\t\t\t\t\t<li><a href='#basics'>Basics</a></li>\n\t\t\t\t\t\t\t<li><a href='#navigation'>Navigation</a></li>\n\t\t\t\t\t\t\t<li><a href='#tabs'>Tabs</a></li>\n\t\t\t\t\t\t\t<li><a href='#bookmarks'>Bookmarks</a></li>\n\t\t\t\t\t\t\t<li><a href='#hotkeys'>Hotkeys</a></li>\n\t\t\t\t\t\t\t<li><a href='#history'>History</a></li>\n\t\t\t\t\t\t\t<li><a href='#downloads'>Downloads</a></li>\n\t\t\t\t\t\t\t<li><a href='#find'>Find</a></li>\n\t\t\t\t\t\t\t<li><a href='#search'>Search</a></li>\n\t\t\t\t\t\t\t<li><a href='#copy'>Copy/Paste</a></li>\n\t\t\t\t\t\t\t<li><a href='#images'>Images</a></li>\n\t\t\t\t\t\t\t<li><a href='#cookies'>Cookies</a></li>\n\t\t\t\t\t\t\t<li><a href='#networking'>Networking</a></li>\n\t\t\t\t\t\t\t<li><a href='#block'>Ad Block</a></li>\n\t\t\t\t\t\t\t<li><a href='#bugs'>Bugs</a></li>\n\t\t\t</td></tr></table>\n\t\t</ul>\n\t\t</td>\n\t\t<td>\n  <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n    <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n    <h4 id='ivervuew'><font color=\"black\">Overview:</font></h4>\n    </td></tr>\n\n    <tr><td WIDTH=\"100%\" bgcolor=\"#FAFAFA\">\n\t\t<p>\n\t\tWelcome to Dillo!  Dillo is a Linux graphical web browser known for its speed and small footprint\n\t\t</p>\n\t\t<br />\n\t\tHere are links to Dillo's <a href='about:splash'>About Page</a> and <a href='https://dillo-browser.github.io/'>Original website</a> and <a href=\"https://github.com/crossbowerbt/dillo-plus/\">Plus flavour</a>.\n\t</td>\n\t</tr>\n    <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n    <h4 id='basics'><font color=\"black\">Basics:</font></h4>\n    </td></tr>\n\n    <tr><td WIDTH=\"100%\" bgcolor=\"#FAFAFA\">\n    <ul>\n      <li> Besides browsing the web, Dillo also has basic file browsing\n           capabilities included. So, entering <code>file:</code> in your\n           Dillo URL window will give you the contents of your current\n           working directory, and <code>file:~</code> entered in the\n           same place will point your Dillo browser right to your home\n           directory...\n\n      <li> Dillo, at this stage of development, is not ready\n           to render pages that use <b><font color=\"#5040a0\">frames</font> directly</b>.\n           When frames are on a webpage, a menu will appear letting you\n           choose which frame to visit, one by one.\n\n      <li> Dillo has <b><font color=\"#5040a0\">context\n           sensitive menus</font></b> using the right  mouse  button\n           (available on pages, links, images, forms, the Back\n           and Forward buttons, and the bug meter).\n\n      <li> Some  of the functions in Dillo are handled by independent\n           processes.  For  instance, Dillo will continue downloading files, via\n           <em><a href=\"http://www.gnu.org/software/wget/\">wget</a></em>.\n           ,If Dillo is exited.\n\n    </ul>\n    </td></tr>\n  </table>\n\n  <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n    <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n    <h4 id='navigation'><font color=\"black\">Page Navigation:</font></h4>\n    </td></tr>\n\n    <tr><td BGCOLOR=\"#FAFAFA\">\n    <ul>\n      <li> You can scroll around your Dillo main window using\n           {PgUp|PgDn|Home|End} or using the mouse middle button\n           or mouse wheel. If nothing happens when keys are pressed, try\n           <b> <font color=\"#5040a0\">focusing</font></b> your Dillo main\n           window first by clicking it (not on any link!:)).\n\n      <li>You can use the space key as PgDn, and {'b' | 'B'} as PgUp.</li>\n\n      <li> Similarly, you can use \"<b>,</b>\" and \"<b>.</b>\" as shortcuts for\n           forward and backward buttons (mnemonic: those\n           keys are usually labeled \"<\" and \">\").\n\n      <li> <b>Configuration:</b> If you want to change Dillo's\n           appearance or behaviour, look at the options in your\n           <b><font color=\"#5040a0\">dillorc</font></b>\n           file (if you don't have a copy in your ~/.dillo/ directory, get it\n           <a href='https://dillo-browser.github.io/old/dillorc'>here</a>).\n\n      <li> Clicking the \"Reload\" button always requests an end-to-end reload\n           of the page currently viewed, but it will *not* reload embedded\n           images during this process.\n\n      <li> Dialogs can be closed with the ESC key.\n           (ESC also hides the findbar and control panels)\n\n      <li> If you want to try a different control panel, right-click over the\n           tools button and select one that suits your needs, then make\n           it the default in your <code>dillorc</code> file.\n\n      <li> The whole window area can be used to display the page (ESC key).\n\n    </ul>\n    </td></tr>\n  </table>\n\n  <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n    <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n    <h4 id='downloads'><font color=\"black\">Downloads:</font></h4>\n    </td></tr>\n\n    <tr><td BGCOLOR=\"#FAFAFA\">\n    <p>\n    Downloads are made using\n    <b><font color=\"#5040a0\">\n    <a href=\"http://www.gnu.org/software/wget/\">wget</a></font></b>\n    with a <a href=\"http://www.fltk.org\">FLTK</a>-based GUI wrapper, through\n    the Dillo plugin (dpi) framework.\n    If you close the browser window, downloads will continue.\n    <p>\n    </td></tr>\n  </table>\n\n  <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n    <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n    <h4 id='find'><font color=\"black\">Find text in the current web page:</font></h4>\n    </td></tr>\n\n    <tr><td bgcolor=\"#FAFAFA\">\n    <p>\n    This one is very useful; it can be found in the right-mouse-button menu.\n    Find text is tuned for speed, so don't hesitate to use it even for minimal\n    searches.\n    <p>\n    <u>Semantics:</u>\n    <ul>\n      <li> You can search for substrings, words and sentences. </li>\n      <li> To find a substring or word, just enter its text. </li>\n      <li> To find a left-aligned substring, prepend it with a space. </li>\n      <li> To find a right-aligned substring, append a space to it.</li>\n      <li> To find full words only, prepend and append spaces to them. </li>\n      <li> To find a sentence, enter the words and remember that\n           the above rules apply for every word in it. </li>\n    </ul>\n    <p>\n    Dillo will scroll the page and highlight found text.\n    <p>\n    <small>Default shortcut: [CTRL]+\"F\".</small>\n\n    </td></tr>\n  </table>\n\n  <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n    <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n    <h4 id='copy'><font color=\"black\">Copy&amp;Paste:</font></h4>\n    </td></tr>\n\n    <tr><td bgcolor=\"#FAFAFA\">\n    <p>\n    Just hold down the left mouse button and move to select the\n    area to copy. To paste, go to the target application and\n    press the middle mouse button.\n    <p>\n    If you want to select more than one screen, hold the mouse button\n    down and scroll with PgUp, PgDn or the arrow keys.\n    <P>\n    If you want to paste an URL into Dillo, do it on the \"clear-URL\"\n    button (the \"X\" next to the location bar).\n    <p>\n    Note: If it doesn't work, please try again. There's a bug lurking there.\n    </td></tr>\n  </table>\n\n  <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n    <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n    <h4 id='history'><font color=\"black\">History:</font></h4>\n    </td></tr>\n\n    <tr><td bgcolor=\"#FAFAFA\">\n    <p>\n    Right-click on the Back or Forward buttons and they will pop up menus showing sites you have visited!\n    <p> <u>Remember:</u>\n    <ul>\n      <li> These history menus are relative to the current page. </li>\n      <li> They show the page-title but the status bar shows the URL. </li>\n      <li> Left-click jumps to the selected item. </li>\n      <li> Middle-click opens the item in a new browser tab/window. </li>\n    </ul>\n    </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4 id='cookies'><font color=\"black\">Cookies:</font></h4>\n   </td></tr>\n\n   <tr><td bgcolor=\"#FAFAFA\">\n   <p>\n   Due to privacy concerns, cookies are disabled by default.\n   That is, if you just compile and use dillo, it will reject\n   every single cookie sent to it!\n   <p>\n   If you want to enable cookies in dillo, please read\n   <a href=\"Cookies.txt\">Cookies.txt</a>. It's very easy --\n   just a matter of setting up a <code>cookiesrc</code> file).\n   </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4 id='networking'><font color=\"black\">Networking:</font></h4>\n   </td></tr>\n   <tr><td bgcolor=\"#FAFAFA\">\n\t   <h4>Proxies</h4>\n\t   <p>\n\t\t\tDillo can use HTTP proxies to route  and secure your browser traffic.  This is accomplished\n\t\t\tby modifiying either the http_proxy environment variable or dillorc variable.  An example dillorc entry would\n\t\t\tlook like the following:\n\t   </p>\n\t   <code>http_proxy=\"https://localhost:9085/\"</code>\n\t\t<h5>Tor Proxy Tunneling</h5>\n\t   <p>\n\t\t   If you would like to use a Tor server, modify your torrc file to include something similar to the following:\n\t   </p>\n\t   <code>HTTPTunnelPort 9085</code>\n\t\t<p><b>Note that all Tor traffic needs HTTPS URLs, not HTTP</b></p>\n\t   <p>\n   </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4 id='block'><font color=\"black\">Ad blocking:</font></h4>\n   </td></tr>\n\n   <tr><td bgcolor=\"#FAFAFA\">\n   <p>\n   With a\n   <code>~/.dillo/<a href=\"https://dillo-browser.github.io/old/domainrc\">domainrc</a></code>\n   file, you can control how dillo handles automatic requests for resources\n   (like images and style sheets) that aren't at the same domain as the\n   original page.\n   </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4 id='tabs'><font color=\"black\">Tabs:</font></h4>\n   </td></tr>\n\n   <tr><td bgcolor=\"#FAFAFA\">\n   <p>\n   Dillo has tabbed browsing. Just middle click to open a link or submit a\n   form in a new tab. It will be automatically focused. If you want to\n   customize this behaviour, adjust these dillorc options:\n   <ul>\n    <li><code>middle_click_opens_new_tab</code>\n    <li><code>focus_new_tab</code>\n   </ul>\n   Press SHIFT to temporarily reverse the focusing behaviour.\n   <p>\n   You can <b><font color=\"#5040a0\">close</font></b>\n   a tab with middle-click on its label (the default),\n   or with right-click by setting this dillorc option:\n   <ul><li><code>right_click_closes_tab</code></ul>\n   </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4 id='images'><font color=\"black\">Images-off mode:</font></h4>\n   </td></tr>\n   <tr><td bgcolor=\"#FAFAFA\">\n   <p>\n   You can browse without images now:\n   <ul>\n   <li>There is an option in the Tools menu to disable automatic image loading.\n   An image's alt text (or <code>[IMG]</code> placeholder) will appear in the\n   page.\n   <li>If you want to load an individual image, left click on its text.\n   <li>You can set \"no images\" as the default mode in dillorc.\n   </ul>\n   </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4  id='bookmarks'><font color=\"black\">Bookmarks:</font></h4>\n   </td></tr>\n\n   <tr><td bgcolor=\"#FAFAFA\">\n   <p>\n   Bookmarks are handled by the Dillo plugin\n   (<a href='https://dillo-browser.github.io/old/dpi1.html'>dpi</a>) framework.\n   This should be transparent to the end user. Please note that:\n   <ul>\n     <li>It is a good idea to keep a backup of your\n         <code>~/.dillo/bm.txt</code>\n     <li>The server will stay alive after closing dillo.\n     <li>You can stop the server by sending it a KILL signal. Dillo\n         will automatically restart it when it is needed.\n     <li>If you don't have root access, install <em>dpid</em> and\n         the <em>dpis</em>\n         inside your <code>~/.dillo/</code> directory as explained\n         in the README file.\n   </ul>\n   </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4 id='bug'><font color=\"black\">Bug Meter:</font></h4>\n   </td></tr>\n   <tr><td bgcolor=\"#FAFAFA\">\n   <p>\n   Dillo's <em><a href='https://dillo-browser.github.io/old/help/bug_meter.html'>Bug\n   meter</a></em> shows the\n   number of <b><font color=\"#5040a0\">detected bugs</font></b> inside the\n   page. The bugs are caught at parsing time, so the\n   error  messages also show the line where they occur and provide a\n   <b><font color=\"#5040a0\">hint</font></b> of what was expected instead!\n   <p>\n   The primary purpose of the bug meter is to\n   <b><font color=\"#5040a0\">help</font></b> webmasters and page\n   authors to polish the contents of their sites with a view to making\n   them standards-compliant.\n   <p>\n   The Bug meter is located at the lower right corner of\n   Dillo. Left-click to see the messages, right-click for a menu.\n   </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4 id='search'><font color=\"black\">Web search from the Location bar:</font></h4>\n   </td></tr>\n   <tr><td bgcolor=\"#FAFAFA\">\n   <p>\n   Besides search features on a webpage you are visitingyou can also use the location bar to\n   quickly search specific websites.  You can specify a prefix in <b><font color=\"#5040a0\">dillorc</font></b>\n   for each search engine and use it from the location bar.\n   <p>\n   Example, in dillorc:<br>\n   <blockquote>\n    <code>search_url=\"dd Duckduckgo http://duckduckgo.com/lite/?kp=-1&amp;q=%s\"</code>\n   </blockquote>\n   means you can reach the location bar (CTRL+L), then type:\n   <blockquote>\n    <code>dd dillo browser</code>\n   </blockquote>\n   to web-search with Duckduckgo for the keywords: <code>dillo browser</code>.\n   </td></tr>\n </table>\n\n <table WIDTH=\"100%\" BORDER=1 CELLSPACING=0 CELLPADDING=3>\n   <tr ALIGN=LEFT VALIGN=TOP><td bgcolor=\"lightgrey\">\n   <h4 id='hotkeys'><font color=\"black\">Keyboard Hotkeys:</font></h4>\n   </td></tr>\n   <tr><td bgcolor=\"#FAFAFA\">\n<table border=1 width=\"100%\">\n<tr><th>Shortcut                   <th>Mnemonic        <th>Function\n<tr><td>Ctrl-L                     <td>Location        <td>enter a new URL\n<tr><td>Ctrl-F or /                     <td>Find            <td>find text in web page\n<tr><td>S                            <td>Search         <td>search the internet\n<tr><td>Ctrl-R                     <td>Reload          <td>reload current page\n<tr><td>Ctrl-N                     <td>New             <td>New browser window\n<tr><td>Ctrl-T                     <td>Tab             <td>New tab\n<tr><td>Ctrl-W                     <td>Window          <td>quit tab/window\n<tr><td>Ctrl-Q                     <td>Quit            <td>Quit dillo\n<tr><td>Alt-F                       <td>File            <td>file menu\n<tr><td>Ctrl-O                     <td>Open            <td>Open file\n<tr><td>Ctrl-S                     <td>Save          <td>Save the webpage's HTML\n<tr><td>Ctrl-U                     <td>(conventional)  <td>view source\n<tr><td>B                     <td>Bookmarks       <td>view bookmarks\n<tr><td>H                     <td>Homepage       <td>go to Home page\n<tr><td>Ctrl-H                     <td>Hide Toolbar       <td>togle the toolbar visibility\n<tr><td>Back       or \"<b>,</b>\"   <td><               <td>previous page\n<tr><td>Shift-Back or \"<b>.</b>\"   <td>>               <td>next page\n<tr><td>Ctrl-TabKey       or\n        Ctrl-PageDown              <td>TabKey          <td>Next tab\n<tr><td>Ctrl-Shift-TabKey or\n        Ctrl-PageUp                <td>TabKey          <td>Previous tab\n<tr><td>Esc                        <td>escape          <td>close dialog,\n                                                           close findbar,<br>\n                                                     Hide/show control panels\n</table>\n<p>\nYou can <b><font color=\"#5040a0\">change</font></b> these bindings by updating the\n<code>~/.dillo/<a href=\"https://dillo-browser.github.io/old/keysrc\">keysrc</a></code> text file.\n   </td></tr>\n </table>\n\t\t</td>\n\t</tr>\n</table>\n\n</body>\n</html>\n\n"
  },
  {
    "path": "dpi/Makefile",
    "content": "include ../Makefile.options\n\nCXXFLAGS_EXTRA = -DDILLO_BINDIR='\"$(DILLO_BINDIR)\"' -DDILLO_LIBDIR='\"$(DILLO_LIBDIR)\"' -DBINNAME='\"$(BINNAME)\"'\n\nall: bookmarks.dpi cookies.dpi datauri.filter.dpi downloads.dpi file.dpi zip.dpi man.dpi ftp.filter.dpi gemini.filter.dpi gopher.filter.dpi hello.filter.dpi vsource.filter.dpi\n\nbookmarks.o: bookmarks.c\n\t$(COMPILE) -DBINNAME='\"$(BINNAME)\"' -c bookmarks.c\n\ndpiutil.o: dpiutil.c dpiutil.h\n\t$(COMPILE) -c dpiutil.c\n\nbookmarks.dpi: bookmarks.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o bookmarks.dpi bookmarks.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\ncookies.o: cookies.c\n\t$(COMPILE) -DBINNAME='\"$(BINNAME)\"' -c cookies.c\n\ncookies.dpi: cookies.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o cookies.dpi cookies.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\ndatauri.o: datauri.c\n\t$(COMPILE) -c datauri.c\n\ndatauri.filter.dpi: datauri.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o datauri.filter.dpi datauri.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\ndownloads_dpi-downloads.o: downloads.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CFLAGS) -DDILLO_LIBDIR='\"$(DILLO_LIBDIR)\"' -DDOWNLOADER_TOOL='\"$(DOWNLOADER_TOOL)\"' -DDOWNLOADER_USER_AGENT_ARG='\"$(DOWNLOADER_USER_AGENT_ARG)\"' -DDOWNLOADER_CONTINUE_ARG='\"$(DOWNLOADER_CONTINUE_ARG)\"' -DDOWNLOADER_LOAD_COOKIES_ARG='\"$(DOWNLOADER_LOAD_COOKIES_ARG)\"' -DDOWNLOADER_OUTPUT_FILENAME_ARG='\"$(DOWNLOADER_OUTPUT_FILENAME_ARG)\"' -DBINNAME='\"$(BINNAME)\"' -o downloads_dpi-downloads.o -c downloads.cc\n\ndownloads.dpi: downloads_dpi-downloads.o  dpiutil.o ../dpip/libDpip.a  ../dlib/libDlib.a\n\t$(CXXCOMPILE) $(LIBFLTK_CFLAGS) $(LIBFLTK_LDFLAGS) -o downloads.dpi downloads_dpi-downloads.o  dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\nfileutil.o: fileutil.c\n\t$(COMPILE) -c fileutil.c\n\nfile.o: file.c\n\t$(COMPILE) -c file.c\n\nfile.dpi: file.o fileutil.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o file.dpi file.o fileutil.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\nzip.o: zip.c\n\t$(COMPILE) -DZIP_USE_7Z='$(ZIP_USE_7Z)' -c zip.c\n\nzip.dpi: zip.o fileutil.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o zip.dpi zip.o fileutil.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\nman.o: man.c\n\t$(COMPILE) -c man.c\n\nman.dpi: man.o fileutil.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o man.dpi man.o fileutil.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\nftp.o: ftp.c\n\t$(COMPILE) -DFTP_USE_WGET='$(FTP_USE_WGET)' -c ftp.c\n\nftp.filter.dpi: ftp.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o ftp.filter.dpi ftp.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\nhello.o: hello.c\n\t$(COMPILE) -c hello.c\n\nhello.filter.dpi: hello.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o hello.filter.dpi hello.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\ngemini.o: gemini.c\n\t$(COMPILE) -DCA_CERTS_FILE='\"$(CA_CERTS_FILE)\"' -DBINNAME='\"$(BINNAME)\"' -c gemini.c\n\ngemini.filter.dpi: gemini.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) $(HTTPS_LDFLAGS) -o gemini.filter.dpi gemini.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\ngopher.o: gopher.c\n\t$(COMPILE) -c gopher.c\n\ngopher.filter.dpi: gopher.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) $(HTTPS_LDFLAGS) -o gopher.filter.dpi gopher.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\nvsource.o: vsource.c\n\t$(COMPILE) -c vsource.c\n\nvsource.filter.dpi: vsource.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(COMPILE) -o vsource.filter.dpi vsource.o dpiutil.o ../dpip/libDpip.a ../dlib/libDlib.a\n\nclean:\n\trm -f *.o *.a *.dpi\n\ninstall: all\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/bookmarks/\"\n\t$(INSTALL) -c bookmarks.dpi \"$(DILLO_LIBDIR)/dpi/bookmarks/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/cookies/\"\n\t$(INSTALL) -c cookies.dpi \"$(DILLO_LIBDIR)/dpi/cookies/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/datauri/\"\n\t$(INSTALL) -c datauri.filter.dpi \"$(DILLO_LIBDIR)/dpi/datauri/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/downloads/\"\n\t$(INSTALL) -c downloads.dpi \"$(DILLO_LIBDIR)/dpi/downloads/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/file/\"\n\t$(INSTALL) -c file.dpi \"$(DILLO_LIBDIR)/dpi/file/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/zip/\"\n\t$(INSTALL) -c zip.dpi \"$(DILLO_LIBDIR)/dpi/zip/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/man/\"\n\t$(INSTALL) -c man.dpi \"$(DILLO_LIBDIR)/dpi/man/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/ftp/\"\n\t$(INSTALL) -c ftp.filter.dpi \"$(DILLO_LIBDIR)/dpi/ftp/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/gemini/\"\n\t$(INSTALL) -c gemini.filter.dpi \"$(DILLO_LIBDIR)/dpi/gemini/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/gopher/\"\n\t$(INSTALL) -c gopher.filter.dpi \"$(DILLO_LIBDIR)/dpi/gopher/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/hello/\"\n\t$(INSTALL) -c hello.filter.dpi \"$(DILLO_LIBDIR)/dpi/hello/\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_LIBDIR)/dpi/vsource/\"\n\t$(INSTALL) -c vsource.filter.dpi \"$(DILLO_LIBDIR)/dpi/vsource/\"\n\nuninstall:\n\trm -f \"$(DILLO_LIBDIR)/dpi/bookmarks/bookmarks.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/cookies/cookies.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/datauri/datauri.filter.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/downloads/downloads.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/file/file.pdi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/zip/zip.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/man/man.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/ftp/ftp.filter.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/gemini/gemini.filter.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/gopher/gopher.filter.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/hello/hello.filter.dpi\"\n\trm -f \"$(DILLO_LIBDIR)/dpi/vsource/vsource.filter.dpi\"\n"
  },
  {
    "path": "dpi/bookmarks.c",
    "content": "/*\n * Bookmarks server (chat version).\n *\n * NOTE: this code illustrates how to make a dpi-program.\n *\n * Copyright 2002-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* TODO: this server is not assembling the received packets.\n * This means it currently expects dillo to send full dpi tags\n * within the socket; if that fails, everything stops.\n * This is not hard to fix, mainly is a matter of expecting the\n * final '>' of a tag.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stddef.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <ctype.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <time.h>\n#include <netdb.h>\n#include <fcntl.h>\n#include <signal.h>\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[bookmarks dpi]: \" __VA_ARGS__)\n\n#define DOCTYPE \\\n   \"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\\n\"\n\n/*\n * Notes on character escaping:\n *   - Basically things are saved unescaped and escaped when in memory.\n *   - &<>\"' are escaped in titles and sections and saved unescaped.\n *   - ' is escaped as %27 in URLs and saved escaped.\n */\ntypedef struct {\n   int key;\n   int section;\n   char *url;\n   char *title;\n} BmRec;\n\ntypedef struct {\n   int section;\n   char *title;\n\n   int o_sec;   /* private, for normalization */\n} BmSec;\n\n\n/*\n * Local data\n */\nstatic char *Header = \"Content-type: text/html\\n\\n\";\nstatic char *BmFile = NULL;\nstatic time_t BmFileTimeStamp = 0;\nstatic Dlist *B_bms = NULL;\nstatic int bm_key = 0;\n\nstatic Dlist *B_secs = NULL;\nstatic int sec_key = 0;\n\nstatic int MODIFY_PAGE_NUM = 1;\n\n\n/*\n * Forward declarations\n */\n\n\n/* -- HTML templates ------------------------------------------------------- */\n\nstatic const char *mainpage_header =\nDOCTYPE\n\"<html>\\n\"\n\"<head>\\n\"\n\"<title>Bookmarks</title>\\n\"\n\"<style>\\n\"\n\"\th1 {\\n\"\n\"\t\tcolor: white;\\n\"\n\"\t\tmargin-left: 1em;\\n\"\n\"\t\ttext-align: center;\\n\"\n\"\t}\\n\"\n\"\th4 {\\n\"\n\"\t\tcolor: #677FFF;\\n\"\n\"\t\tmargin-left: 1em;\\n\"\n\"\t\ttext-align: center;\\n\"\n\"\t}\\n\"\n\"\ta:visited {\\n\"\n\"\t  color: #262626;\\n\"\n\"\t}\\n\"\n\"</style>\\n\"\n\"</head>\\n\"\n\"<body id='dillo_bm' bgcolor='#989DA4' link='black' vlink='brown'>\\n\"\n\"<table border='0' cellpadding='0' width='100%'>\\n\"\n\" <tr><td>\\n\"\n\"  <table width='100%' bgcolor='transparent;'>\\n\"\n\"   <tr>\\n\"\n\"    <td><h1> Bookmarks ::  </h1></td>\\n\"\n\"    <td align='right'>\\n\"\n\"     [<a href='dpi:/bm/modify'>modify</a>]\\n\"\n\"    </td></tr>\\n\"\n\"  </table></td></tr>\\n\"\n\"</table>\\n\"\n\"<br>\\n\";\n\nstatic const char *modifypage_header =\nDOCTYPE\n\"<html>\\n\"\n\"<head>\\n\"\n\"<title>Bookmarks</title>\\n\"\n\"<style>\\n\"\n\"\th1 {\\n\"\n\"\t\tcolor: white;\\n\"\n\"\t\tmargin-left: 1em;\\n\"\n\"\t\ttext-align: center;\\n\"\n\"\t}\\n\"\n\"\th4 {\\n\"\n\"\t\tcolor: #677FFF;\\n\"\n\"\t\tmargin-left: 1em;\\n\"\n\"\t\ttext-align: center;\\n\"\n\"\t}\\n\"\n\"\ta:visited {\\n\"\n\"\t  color: #262626;\\n\"\n\"\t}\\n\"\n\"</style>\\n\"\n\"</head>\\n\"\n\"<body id='dillo_bm' bgcolor='#989DA4' link='black' vlink='brown'>\\n\"\n\"<table border='0' cellpadding='0' width='100%'>\\n\"\n\" <tr><td>\\n\"\n\"  <table width='100%' bgcolor='transparent;'>\\n\"\n\"   <tr>\\n\"\n\"    <td><h1> Bookmarks :: modify</h1></td>\\n\"\n\"    <td align='right'>\\n\"\n\"     [<a href='dpi:/bm/'>cancel</a>]\\n\"\n\"    </td>\\n\"\n\"   </tr>\\n\"\n\"  </table></td></tr>                            \\n\"\n\"</table>                                        \\n\"\n\"\\n\"\n\"<form action='modify'>\\n\"\n\"<table width='100%' border='0' cellpadding='0'>\\n\"\n\" <tr style='background-color: #D6D6D6'>\\n\"\n\"  <td>\\n\"\n\"   <b>Select an operation</b>\\n\"\n\"   <select name='operation'>\\n\"\n\"    <option value='none' selected>--\\n\"\n\"    <option value='delete'>Delete\\n\"\n\"    <option value='move'>Move\\n\"\n\"    <option value='modify'>Modify\\n\"\n\"    <option value='add_sec'>Add Section\\n\"\n\"    <option value='add_url'>Add URL\\n\"\n\"   </select>\\n\"\n\"   <b>, mark its operands, and</b>\\n\"\n\"   <input type='submit' name='submit' value='submit.'>\\n\"\n\"  </td>\\n\"\n\" </tr>\\n\"\n\"</table>\\n\";\n\nstatic const char *mainpage_sections_header =\n\"<table border='0' cellpadding='0' cellspacing='20' width='100%'>\\n\"\n\" <tr valign='top'>\\n\"\n\"  <td>\\n\"\n\"   <table bgcolor='#F7F7F7' border='0' cellpadding='4' cellspacing='1'>\\n\"\n\"    <tr><td>\\n\"\n\"     <table bgcolor='#4F4F4F'>\\n\"\n\"      <tr><td><b style='color: white;'>Sections:</b></td></tr></table></td></tr>\\n\";\n\nstatic const char *modifypage_sections_header =\n\"<table border='0' cellpadding='0' cellspacing='20' width='100%'>\\n\"\n\" <tr valign='top'>\\n\"\n\"  <td>\\n\"\n\"   <table bgcolor='#F7F7F7' border='0'>\\n\"\n\"    <tr><td>\\n\"\n\"     <table width='100%' bgcolor='#4F4F4F'>\\n\"\n\"      <tr><td><b style='color: white;'>Sections:</b></td></tr></table></td></tr>\\n\";\n\nstatic const char *mainpage_sections_item =\n\"    <tr><td align='center'>\\n\"\n\"      <a href='#s%d'>%s</a></td></tr>\\n\";\n\nstatic const char *modifypage_sections_item =\n\"    <tr><td>\\n\"\n\"     <table width='100%%'>\\n\"\n\"      <tr align='center'>\"\n\"       <td><input type='checkbox' name='s%d'></td>\\n\"\n\"       <td width='100%%'><a href='#s%d'>%s</a></td></tr></table></td></tr>\\n\";\n\nstatic const char *mainpage_sections_footer =\n\"   </table>\\n\";\n\nstatic const char *modifypage_sections_footer =\n\"   </table>\\n\";\n\nstatic const char *mainpage_middle1 =\n\"  </td>\\n\"\n\"  <td width='100%'>\\n\";\n\nstatic const char *modifypage_middle1 =\n\"  </td>\\n\"\n\"  <td width='100%'>\\n\";\n\nstatic const char *mainpage_section_card_header =\n\"   <a name='s%d'></a>\\n\"\n\"   <table bgcolor='#F1F3FA' width='100%%' cellspacing='0'>\\n\"\n\"    <tr>\\n\"\n\"     <td bgcolor='#4F4F4F'><font color='white'><b>\\n\"\n\"      &nbsp;&nbsp;&nbsp;%s&nbsp;&nbsp;&nbsp;</b></font></td>\\n\"\n\"     <td bgcolor='#4F4F4F' width='100%%'>&nbsp;</td></tr>\\n\";\n\nstatic const char *modifypage_section_card_header =\n\"   <a name='s%d'></a>\\n\"\n\"   <table bgcolor='#F1F3FA' width='100%%' cellspacing='0'>\\n\"\n\"    <tr>\\n\"\n\"     <td bgcolor='#4F4F4F'><font color='white'><b>\\n\"\n\"      &nbsp;&nbsp;&nbsp;%s&nbsp;&nbsp;&nbsp;</b></font></td>\\n\"\n\"     <td bgcolor='#4F4F4F' width='100%%'>&nbsp;</td></tr>\\n\";\n\nstatic const char *mainpage_section_card_item =\n\"    <tr><td colspan='2'>\\n\"\n\"      <a href='%s'>%s</a> </td></tr>\\n\";\n\nstatic const char *modifypage_section_card_item =\n\"    <tr>\\n\"\n\"     <td colspan='2'><input type='checkbox' name='url%d'>\\n\"\n\"      <a href='%s'>%s</a></td></tr>\\n\";\n\nstatic const char *mainpage_section_card_footer =\n\"   </table>\\n\"\n\"   <hr>\\n\";\n\nstatic const char *modifypage_section_card_footer =\n\"   </table>\\n\"\n\"   <hr>\\n\";\n\nstatic const char *mainpage_footer =\n\"  </td>\\n\"\n\" </tr>\\n\"\n\"</table>\\n\"\n\"</body>\\n\"\n\"</html>\\n\";\n\nstatic const char *modifypage_footer =\n\"  </td>\\n\"\n\" </tr>\\n\"\n\"</table>\\n\"\n\"</form>\\n\"\n\"</body>\\n\"\n\"</html>\\n\";\n\n/* ------------------------------------------------------------------------- */\nstatic const char *modifypage_add_section_page =\nDOCTYPE\n\"<html>\\n\"\n\"<head>\\n\"\n\"<title>Bookmarks</title>\\n\"\n\"</head>\\n\"\n\"<body id='dillo_bm' bgcolor='transparent;' link='black' vlink='brown'>\\n\"\n\"<table border='0' cellpadding='0' width='100%'>\\n\"\n\" <tr><td colspan='2'>\\n\"\n\"  <table bgcolor='#999BDE' width='100%'>\\n\"\n\"   <tr>\\n\"\n\"    <td bgcolor='#4F4F4F'><h1>\\n\"\n\"     Modify bookmarks :: add section</h1>\\n\"\n\"    </td>\\n\"\n\"    <td align='right'>\\n\"\n\"     [<a href='dpi:/bm/'>cancel</a>]\\n\"\n\"    </td>\\n\"\n\"   </tr>\\n\"\n\"  </table></td></tr>\\n\"\n\"</table>\\n\"\n\"<br>\\n\"\n\"<form action='modify'>\\n\"\n\" <input type='hidden' name='operation' value='add_section'>\\n\"\n\"<table border='0' width='100%'>\\n\"\n\" <tr>\\n\"\n\"  <td bgcolor='#7D9BAF'><b>New&nbsp;section:</b></td>\\n\"\n\"  <td bgcolor='#4F4F4F' width='100%'></td></tr>\\n\"\n\"</table>\\n\"\n\"<table width='100%' cellpadding='10'>\\n\"\n\"<tr><td>\\n\"\n\" <table width='100%' bgcolor='#D6D6D6'>\\n\"\n\"  <tr>\\n\"\n\"   <td>Title:</td>\\n\"\n\"   <td><input type='text' name='title' size='64'></td></tr>\\n\"\n\" </table>\\n\"\n\" </td></tr>\\n\"\n\"</table>\\n\"\n\"<table width='100%' cellpadding='4' border='0'>\\n\"\n\"<tr><td bgcolor='#a0a0a0'>\\n\"\n\" <input type='submit' name='submit' value='submit.'></td></tr>\\n\"\n\"</table>\\n\"\n\"</form>\\n\"\n\"</body>\\n\"\n\"</html>\\n\"\n\"\\n\";\n\n/* ------------------------------------------------------------------------- */\nstatic const char *modifypage_update_header =\nDOCTYPE\n\"<html>\\n\"\n\"<head>\\n\"\n\"<title>Bookmarks</title>\\n\"\n\"</head>\\n\"\n\"<body id='dillo_bm' bgcolor='transparent;' link='black' vlink='brown'>\\n\"\n\"<table border='0' cellpadding='0' width='100%'>\\n\"\n\" <tr><td colspan='2'>\\n\"\n\"  <table bgcolor='#999BDE' width='100%'>\\n\"\n\"   <tr><td bgcolor='#4F4F4F'><h1> Modify bookmarks :: update</h1>\\n\"\n\"    </td>\\n\"\n\"    <td align='right'>\\n\"\n\"     [<a href='dpi:/bm/'>cancel</a>]\\n\"\n\"    </td>\\n\"\n\"   </tr>\\n\"\n\"  </table></td></tr>\\n\"\n\"</table>\\n\"\n\"<br>\\n\"\n\"<form action='modify'>\\n\"\n\"<input type='hidden' name='operation' value='modify2'>\\n\";\n\nstatic const char *modifypage_update_title =\n\"<table border='0' width='100%%'>\\n\"\n\" <tr>\\n\"\n\"  <td bgcolor='#7D9BAF'><b>%s</b></td>\\n\"\n\"  <td bgcolor='#4F4F4F' width='100%%'></td></tr>\\n\"\n\"</table>\\n\";\n\nstatic const char *modifypage_update_item_header =\n\"<table width='100%' cellpadding='10'>\\n\";\n\nstatic const char *modifypage_update_item =\n\"<tr><td>\\n\"\n\" <table width='100%%' bgcolor='#D6D6D6'>\\n\"\n\"  <tr>\\n\"\n\"   <td>Title:</td>\\n\"\n\"   <td><input type='text' name='title%d' size='64'\\n\"\n\"        value='%s'></td></tr>\\n\"\n\"  <tr>\\n\"\n\"   <td>URL:</td>\\n\"\n\"   <td>%s</td></tr>\\n\"\n\" </table>\\n\"\n\" </td></tr>\\n\";\n\nstatic const char *modifypage_update_item2 =\n\"<tr><td>\\n\"\n\" <table width='100%%' bgcolor='#D6D6D6'>\\n\"\n\"  <tr>\\n\"\n\"   <td>Title:</td>\\n\"\n\"   <td><input type='text' name='s%d' size='64'\\n\"\n\"        value='%s'></td></tr>\\n\"\n\" </table>\\n\"\n\" </td></tr>\\n\";\n\nstatic const char *modifypage_update_item_footer =\n\"</table>\\n\";\n\nstatic const char *modifypage_update_footer =\n\"<table width='100%' cellpadding='4' border='0'>\\n\"\n\"<tr><td bgcolor='#a0a0a0'>\\n\"\n\" <input type='submit' name='submit' value='submit.'></td></tr>\\n\"\n\"</table>\\n\"\n\"</form>\\n\"\n\"</body>\\n\"\n\"</html>\\n\";\n\n/* ------------------------------------------------------------------------- */\nstatic const char *modifypage_add_url =\nDOCTYPE\n\"<html>\\n\"\n\"<head>\\n\"\n\"<title>Bookmarks</title>\\n\"\n\"</head>\\n\"\n\"<body id='dillo_bm' bgcolor='transparent;' link='black' vlink='brown'>\\n\"\n\"<table border='0' cellpadding='0' width='100%'>\\n\"\n\" <tr><td colspan='2'>\\n\"\n\"  <table bgcolor='#999BDE' width='100%'>\\n\"\n\"   <tr><td bgcolor='#4F4F4F'><h1> Modify bookmarks :: add url</h1>\\n\"\n\"    </td>\\n\"\n\"    <td align='right'>\\n\"\n\"     [<a href='dpi:/bm/'>cancel</a>]\\n\"\n\"    </td>\\n\"\n\"   </tr>\\n\"\n\"  </table></td></tr>\\n\"\n\"</table>\\n\"\n\"<br>\\n\"\n\"<form action='modify'>\\n\"\n\"<input type='hidden' name='operation' value='add_url2'>\\n\"\n\"<table border='0' width='100%'>\\n\"\n\" <tr>\\n\"\n\"  <td bgcolor='#7D9BAF'><b>Add&nbsp;url:</b></td>\\n\"\n\"  <td bgcolor='#4F4F4F' width='100%'></td></tr>\\n\"\n\"</table>\\n\"\n\"<table width='100%' cellpadding='10'>\\n\"\n\"<tr><td>\\n\"\n\" <table width='100%' bgcolor='#D6D6D6'>\\n\"\n\"  <tr>\\n\"\n\"   <td>Title:</td>\\n\"\n\"   <td><input type='text' name='title' size='64'></td></tr>\\n\"\n\"  <tr>\\n\"\n\"   <td>URL:</td>\\n\"\n\"   <td><input type='text' name='url' size='64'></td></tr>\\n\"\n\" </table>\\n\"\n\" </td></tr>\\n\"\n\"</table>\\n\"\n\"<table width='100%' cellpadding='4' border='0'>\\n\"\n\"<tr><td bgcolor='#a0a0a0'>\\n\"\n\" <input type='submit' name='submit' value='submit.'></td></tr>\\n\"\n\"</table>\\n\"\n\"</form>\\n\"\n\"</body>\\n\"\n\"</html>\\n\";\n\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Return a new string with spaces changed with &nbsp;\n */\nstatic char *make_one_line_str(char *str)\n{\n   char *new_str;\n   int i, j, n;\n\n   for (i = 0, n = 0; str[i]; ++i)\n      if (str[i] == ' ')\n         ++n;\n\n   new_str = dNew(char, strlen(str) + 6*n + 1);\n   new_str[0] = 0;\n\n   for (i = 0, j = 0; str[i]; ++i) {\n      if (str[i] == ' ') {\n         strcpy(new_str + j, \"&nbsp;\");\n         j += 6;\n      } else {\n         new_str[j] = str[i];\n         new_str[++j] = 0;\n      }\n   }\n\n   return new_str;\n}\n\n/*\n * Given an urlencoded string, return it to the original version.\n */\nstatic void Unencode_str(char *e_str)\n{\n   char *p, *e;\n\n   for (p = e = e_str; *e; e++, p++) {\n      if (*e == '+') {\n         *p = ' ';\n      } else if (*e == '%') {\n         if (dStrnAsciiCasecmp(e, \"%0D%0A\", 6) == 0) {\n            *p = '\\n';\n            e += 5;\n         } else {\n            *p = (isdigit(e[1]) ? (e[1] - '0') : (e[1] - 'A' + 10)) * 16 +\n                 (isdigit(e[2]) ? (e[2] - '0') : (e[2] - 'A' + 10));\n            e += 2;\n         }\n      } else {\n         *p = *e;\n      }\n   }\n   *p = 0;\n}\n\n/*\n * Send a short message to dillo's status bar.\n */\nstatic int Bmsrv_dpi_send_status_msg(Dsh *sh, char *str)\n{\n   int st;\n   char *d_cmd;\n\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"send_status_message\", str);\n   st = a_Dpip_dsh_write_str(sh, 1, d_cmd);\n   dFree(d_cmd);\n   return st;\n}\n\n/* -- ADT for bookmarks ---------------------------------------------------- */\n/*\n * Compare function for searching a bookmark by its key\n */\nstatic int Bms_node_by_key_cmp(const void *node, const void *key)\n{\n   return ((BmRec *)node)->key - VOIDP2INT(key);\n}\n\n/*\n * Compare function for searching a bookmark by section\n */\nstatic int Bms_node_by_section_cmp(const void *node, const void *key)\n{\n   return ((BmRec *)node)->section - VOIDP2INT(key);\n}\n\n/*\n * Compare function for searching a section by its number\n */\nstatic int Bms_sec_by_number_cmp(const void *node, const void *key)\n{\n   return ((BmSec *)node)->section - VOIDP2INT(key);\n}\n\n/*\n * Return the Bm record by key\n */\nstatic BmRec *Bms_get(int key)\n{\n   return dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp);\n}\n\n/*\n * Return the Section record by key\n */\nstatic BmSec *Bms_get_sec(int key)\n{\n   return dList_find_custom(B_secs, INT2VOIDP(key), Bms_sec_by_number_cmp);\n}\n\n/*\n * Add a bookmark\n */\nstatic void Bms_add(int section, char *url, char *title)\n{\n   BmRec *bm_node;\n\n   bm_node = dNew(BmRec, 1);\n   bm_node->key = ++bm_key;\n   bm_node->section = section;\n   bm_node->url = Escape_uri_str(url, \"'\");\n   bm_node->title = Escape_html_str(title);\n   dList_append(B_bms, bm_node);\n}\n\n/*\n * Add a section\n */\nstatic void Bms_sec_add(char *title)\n{\n   BmSec *sec_node;\n\n   sec_node = dNew(BmSec, 1);\n   sec_node->section = sec_key++;\n   sec_node->title = Escape_html_str(title);\n   dList_append(B_secs, sec_node);\n}\n\n/*\n * Delete a bookmark by its key\n */\nstatic void Bms_del(int key)\n{\n   BmRec *bm_node;\n\n   bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp);\n   if (bm_node) {\n      dList_remove(B_bms, bm_node);\n      dFree(bm_node->title);\n      dFree(bm_node->url);\n      dFree(bm_node);\n   }\n   if (dList_length(B_bms) == 0)\n      bm_key = 0;\n}\n\n/*\n * Delete a section and its bookmarks by section number\n */\nstatic void Bms_sec_del(int section)\n{\n   BmSec *sec_node;\n   BmRec *bm_node;\n\n   sec_node = dList_find_custom(B_secs, INT2VOIDP(section),\n                                Bms_sec_by_number_cmp);\n   if (sec_node) {\n      dList_remove(B_secs, sec_node);\n      dFree(sec_node->title);\n      dFree(sec_node);\n\n      /* iterate B_bms and remove those that match the section */\n      while ((bm_node = dList_find_custom(B_bms, INT2VOIDP(section),\n                                          Bms_node_by_section_cmp))) {\n         Bms_del(bm_node->key);\n      }\n   }\n   if (dList_length(B_secs) == 0)\n      sec_key = 0;\n}\n\n/*\n * Move a bookmark to another section\n */\nstatic void Bms_move(int key, int target_section)\n{\n   BmRec *bm_node;\n\n   bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp);\n   if (bm_node) {\n      bm_node->section = target_section;\n   }\n}\n\n/*\n * Update a bookmark title by key\n */\nstatic void Bms_update_title(int key, char *n_title)\n{\n   BmRec *bm_node;\n\n   bm_node = dList_find_custom(B_bms, INT2VOIDP(key), Bms_node_by_key_cmp);\n   if (bm_node) {\n      dFree(bm_node->title);\n      bm_node->title = Escape_html_str(n_title);\n   }\n}\n\n/*\n * Update a section title by key\n */\nstatic void Bms_update_sec_title(int key, char *n_title)\n{\n   BmSec *sec_node;\n\n   sec_node = dList_find_custom(B_secs, INT2VOIDP(key), Bms_sec_by_number_cmp);\n   if (sec_node) {\n      dFree(sec_node->title);\n      sec_node->title = Escape_html_str(n_title);\n   }\n}\n\n/*\n * Free all the bookmarks data (bookmarks and sections)\n */\nstatic void Bms_free(void)\n{\n   BmRec *bm_node;\n   BmSec *sec_node;\n\n   /* free B_bms */\n   while ((bm_node = dList_nth_data(B_bms, 0))) {\n      Bms_del(bm_node->key);\n   }\n   /* free B_secs */\n   while ((sec_node = dList_nth_data(B_secs, 0))) {\n      Bms_sec_del(sec_node->section);\n   }\n}\n\n/*\n * Enforce increasing correlative section numbers with no jumps.\n */\nstatic void Bms_normalize(void)\n{\n   BmRec *bm_node;\n   BmSec *sec_node;\n   int i, j;\n\n   /* we need at least one section */\n   if (dList_length(B_secs) == 0)\n      Bms_sec_add(\"Unclassified\");\n\n   /* make correlative section numbers */\n   for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) {\n      sec_node->o_sec = sec_node->section;\n      sec_node->section = i;\n   }\n\n   /* iterate B_secs and make the changes in B_bms */\n   for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) {\n      if (sec_node->section != sec_node->o_sec) {\n         /* update section numbers */\n         for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) {\n            if (bm_node->section == sec_node->o_sec)\n               bm_node->section = sec_node->section;\n         }\n      }\n   }\n}\n\n/* -- Load bookmarks file -------------------------------------------------- */\n\n/*\n * If there's no \"bm.txt\", create one from \"bookmarks.html\".\n */\nstatic void Bms_check_import(void)\n{\n   char *OldBmFile;\n   char *cmd1 =\n      \"echo \\\":s0: Unclassified\\\" > %s\";\n   char *cmd2 =\n      \"grep -i \\\"href\\\" %s | \"\n      \"sed -e 's/<li><A HREF=\\\"/s0 /' -e 's/\\\">/ /' -e 's/<.*$//' >> %s\";\n   Dstr *dstr = dStr_new(\"\");\n   int rc;\n\n\n   if (access(BmFile, F_OK) != 0) {\n      OldBmFile = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/bookmarks.html\", NULL);\n      if (access(OldBmFile, F_OK) == 0) {\n         dStr_sprintf(dstr, cmd1, BmFile);\n         rc = system(dstr->str);\n         if (rc == 127) {\n            MSG(\"Bookmarks: /bin/sh could not be executed\\n\");\n         } else if (rc == -1) {\n            MSG(\"Bookmarks: process creation failure: %s\\n\",\n                dStrerror(errno));\n         }\n         dStr_sprintf(dstr, cmd2, OldBmFile, BmFile);\n         rc = system(dstr->str);\n         if (rc == 127) {\n            MSG(\"Bookmarks: /bin/sh could not be executed\\n\");\n         } else if (rc == -1) {\n            MSG(\"Bookmarks: process creation failure: %s\\n\",\n                dStrerror(errno));\n         }\n\n         dStr_free(dstr, TRUE);\n         dFree(OldBmFile);\n      }\n   }\n}\n\n/*\n * Load bookmarks data from a file\n */\nstatic int Bms_load(void)\n{\n   FILE *BmTxt;\n   char *buf, *p, *url, *title, *u_title;\n   int section;\n   struct stat TimeStamp;\n\n   /* clear current bookmarks */\n   Bms_free();\n\n   /* open bm file */\n   if (!(BmTxt = fopen(BmFile, \"r\"))) {\n      perror(\"[fopen]\");\n      return 1;\n   }\n\n   /* load bm file into memory */\n   while ((buf = dGetline(BmTxt)) != NULL) {\n      if (buf[0] == 's') {\n         /* get section, url and title */\n         section = strtol(buf + 1, NULL, 10);\n         p = strchr(buf, ' ');\n         if (!p)\n            goto error;\n         *p = 0;\n         url = ++p;\n         p = strchr(p, ' ');\n         if (!p)\n            goto error;\n         *p = 0;\n         title = ++p;\n         p = strchr(p, '\\n');\n         if (!p)\n            goto error;\n         *p = 0;\n         u_title = Unescape_html_str(title);\n         Bms_add(section, url, u_title);\n         dFree(u_title);\n\n      } else if (buf[0] == ':' && buf[1] == 's') {\n         /* section = strtol(buf + 2, NULL, 10); */\n         p = strchr(buf + 2, ' ');\n         if (!p)\n            goto error;\n         title = ++p;\n         p = strchr(p, '\\n'); *p = 0;\n         if (!p)\n            goto error;\n         Bms_sec_add(title);\n      } else {\n         goto error;\n      }\n\n      dFree(buf);\n      continue;\n\nerror:\n      MSG(\"Syntax error in bookmarks file:\\n %s\", buf);\n      dFree(buf);\n   }\n   fclose(BmTxt);\n\n   /* keep track of the timestamp */\n   stat(BmFile, &TimeStamp);\n   BmFileTimeStamp = TimeStamp.st_mtime;\n\n   return 0;\n}\n\n/*\n * Load bookmarks data if:\n *   - file timestamp is newer than ours  or\n *   - we haven't loaded anything yet :)\n */\nstatic int Bms_cond_load(void)\n{\n   int st = 0;\n   struct stat TimeStamp;\n\n   if (stat(BmFile, &TimeStamp) != 0) {\n      /* try to import... */\n      Bms_check_import();\n      if (stat(BmFile, &TimeStamp) != 0)\n         TimeStamp.st_mtime = 0;\n   }\n\n   if (!BmFileTimeStamp || !dList_length(B_bms) || !dList_length(B_secs) ||\n       BmFileTimeStamp < TimeStamp.st_mtime) {\n      Bms_load();\n      st = 1;\n   }\n   return st;\n}\n\n/* -- Save bookmarks file -------------------------------------------------- */\n\n/*\n * Update the bookmarks file from memory contents\n * Return code: { 0:OK, 1:Abort }\n */\nstatic int Bms_save(void)\n{\n   FILE *BmTxt;\n   BmRec *bm_node;\n   BmSec *sec_node;\n   struct stat BmStat;\n   char *u_title;\n   int i, j;\n   Dstr *dstr = dStr_new(\"\");\n\n   /* make a safety backup */\n   if (stat(BmFile, &BmStat) == 0 && BmStat.st_size > 256) {\n      char *BmFileBak = dStrconcat(BmFile, \".bak\", NULL);\n      rename(BmFile, BmFileBak);\n      dFree(BmFileBak);\n   }\n\n   /* open bm file */\n   if (!(BmTxt = fopen(BmFile, \"w\"))) {\n      perror(\"[fopen]\");\n      return 1;\n   }\n\n   /* normalize */\n   Bms_normalize();\n\n   /* save sections */\n   for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) {\n      u_title = Unescape_html_str(sec_node->title);\n      dStr_sprintf(dstr, \":s%d: %s\\n\", sec_node->section, u_title);\n      fwrite(dstr->str, (size_t)dstr->len, 1, BmTxt);\n      dFree(u_title);\n   }\n\n   /* save bookmarks  (section url title) */\n   for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) {\n      for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) {\n         if (bm_node->section == sec_node->section) {\n            u_title = Unescape_html_str(bm_node->title);\n            dStr_sprintf(dstr, \"s%d %s %s\\n\",\n                         bm_node->section, bm_node->url, u_title);\n            fwrite(dstr->str, (size_t)dstr->len, 1, BmTxt);\n            dFree(u_title);\n         }\n      }\n   }\n\n   dStr_free(dstr, TRUE);\n   fclose(BmTxt);\n\n   /* keep track of the timestamp */\n   stat(BmFile, &BmStat);\n   BmFileTimeStamp = BmStat.st_mtime;\n\n   return 0;\n}\n\n/* -- Add bookmark --------------------------------------------------------- */\n\n/*\n * Add a new bookmark to DB :)\n */\nstatic int Bmsrv_add_bm(Dsh *sh, char *url, char *title)\n{\n   char *u_title;\n   char *msg=\"Added bookmark!\";\n   int section = 0;\n\n   /* Add in memory */\n   u_title = Unescape_html_str(title);\n   Bms_add(section, url, u_title);\n   dFree(u_title);\n\n   /* Write to file */\n   Bms_save();\n\n   if (Bmsrv_dpi_send_status_msg(sh, msg))\n      return 1;\n\n   return 0;\n}\n\n/* -- Modify --------------------------------------------------------------- */\n\n/*\n * Count how many sections and urls were marked in a request\n */\nstatic void Bmsrv_count_urls_and_sections(char *url, int *n_sec, int *n_url)\n{\n   char *p, *q;\n   int i;\n\n   /* Check marked urls and sections */\n   *n_sec = *n_url = 0;\n   if ((p = strchr(url, '?'))) {\n      for (q = p; (q = strstr(q, \"&url\")); ++q) {\n         for (i = 0; isdigit(q[4+i]); ++i);\n         *n_url += (q[4+i] == '=') ? 1 : 0;\n      }\n      for (q = p; (q = strstr(q, \"&s\")); ++q) {\n         for (i = 0; isdigit(q[2+i]); ++i);\n         *n_sec += (q[2+i] == '=') ? 1 : 0;\n      }\n   }\n}\n\n/*\n * Send a dpi reload request\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int Bmsrv_send_reload_request(Dsh *sh, char *url)\n{\n   int st;\n   char *d_cmd;\n\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"reload_request\", url);\n   st = a_Dpip_dsh_write_str(sh, 1, d_cmd) ? 1 : 0;\n   dFree(d_cmd);\n   return st;\n}\n\n/*\n * Send the HTML for the modify page\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int Bmsrv_send_modify_page(Dsh *sh)\n{\n   static Dstr *dstr = NULL;\n   char *l_title;\n   BmSec *sec_node;\n   BmRec *bm_node;\n   int i, j;\n\n   if (!dstr)\n      dstr = dStr_new(\"\");\n\n   /* send modify page header */\n   if (a_Dpip_dsh_write_str(sh, 0, modifypage_header))\n      return 1;\n\n   /* write sections header */\n   if (a_Dpip_dsh_write_str(sh, 0, modifypage_sections_header))\n      return 1;\n   /* write sections */\n   for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) {\n      dStr_sprintf(dstr, modifypage_sections_item,\n                   sec_node->section, sec_node->section, sec_node->title);\n      if (a_Dpip_dsh_write_str(sh, 0, dstr->str))\n         return 1;\n   }\n   /* write sections footer */\n   if (a_Dpip_dsh_write_str(sh, 0, modifypage_sections_footer))\n      return 1;\n\n   /* send page middle */\n   if (a_Dpip_dsh_write_str(sh, 0, modifypage_middle1))\n      return 1;\n\n   /* send bookmark cards */\n   for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) {\n      /* send card header */\n      l_title = make_one_line_str(sec_node->title);\n      dStr_sprintf(dstr, modifypage_section_card_header,\n                   sec_node->section, l_title);\n      dFree(l_title);\n      if (a_Dpip_dsh_write_str(sh, 0, dstr->str))\n         return 1;\n\n      /* send section's bookmarks */\n      for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) {\n         if (bm_node->section == sec_node->section) {\n            dStr_sprintf(dstr, modifypage_section_card_item,\n                         bm_node->key, bm_node->url, bm_node->title);\n            if (a_Dpip_dsh_write_str(sh, 0, dstr->str))\n               return 1;\n         }\n      }\n\n      /* send card footer */\n      if (a_Dpip_dsh_write_str(sh, 0, modifypage_section_card_footer))\n         return 1;\n   }\n\n   /* finish page */\n   if (a_Dpip_dsh_write_str(sh, 1, modifypage_footer))\n      return 1;\n\n   return 2;\n}\n\n/*\n * Send the HTML for the modify page for \"add section\"\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int Bmsrv_send_modify_page_add_section(Dsh *sh)\n{\n   /* send modify page2 */\n   if (a_Dpip_dsh_write_str(sh, 1, modifypage_add_section_page))\n      return 1;\n\n   return 2;\n}\n\n/*\n * Send the HTML for the modify page for \"add url\"\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int Bmsrv_send_modify_page_add_url(Dsh *sh)\n{\n   if (a_Dpip_dsh_write_str(sh, 1, modifypage_add_url))\n      return 1;\n   return 2;\n}\n\n/*\n * Parse a modify urls request and either:\n *   - make a local copy of the url\n *     or\n *   - send the modify page for the marked urls and sections\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int Bmsrv_send_modify_update(Dsh *sh, char *url)\n{\n   static char *url1 = NULL;\n   static Dstr *dstr = NULL;\n   char *p, *q;\n   int i, key, n_sec, n_url;\n   BmRec *bm_node;\n   BmSec *sec_node;\n\n   /* bookmarks were loaded before */\n\n   if (!dstr)\n      dstr = dStr_new(\"\");\n\n   if (sh == NULL) {\n      /* just copy url */\n      dFree(url1);\n      url1 = dStrdup(url);\n      return 0;\n   }\n\n   /* send HTML here */\n   if (a_Dpip_dsh_write_str(sh, 0, modifypage_update_header))\n      return 1;\n\n   /* Count number of marked urls and sections */\n   Bmsrv_count_urls_and_sections(url1, &n_sec, &n_url);\n\n   if (n_sec) {\n      dStr_sprintf(dstr, modifypage_update_title, \"Update&nbsp;sections:\");\n      a_Dpip_dsh_write_str(sh, 0, dstr->str);\n      a_Dpip_dsh_write_str(sh, 0, modifypage_update_item_header);\n      /* send items here */\n      p = strchr(url1, '?');\n      for (q = p; (q = strstr(q, \"&s\")); ++q) {\n         for (i = 0; isdigit(q[2+i]); ++i);\n         if (q[2+i] == '=') {\n            key = strtol(q + 2, NULL, 10);\n            if ((sec_node = Bms_get_sec(key))) {\n               dStr_sprintf(dstr, modifypage_update_item2,\n                            sec_node->section, sec_node->title);\n               a_Dpip_dsh_write_str(sh, 0, dstr->str);\n            }\n         }\n      }\n      a_Dpip_dsh_write_str(sh, 0, modifypage_update_item_footer);\n   }\n\n   if (n_url) {\n      dStr_sprintf(dstr, modifypage_update_title, \"Update&nbsp;titles:\");\n      a_Dpip_dsh_write_str(sh, 0, dstr->str);\n      a_Dpip_dsh_write_str(sh, 0, modifypage_update_item_header);\n      /* send items here */\n      p = strchr(url1, '?');\n      for (q = p; (q = strstr(q, \"&url\")); ++q) {\n         for (i = 0; isdigit(q[4+i]); ++i);\n         if (q[4+i] == '=') {\n            key = strtol(q + 4, NULL, 10);\n            bm_node = Bms_get(key);\n            dStr_sprintf(dstr, modifypage_update_item,\n                         bm_node->key, bm_node->title, bm_node->url);\n            a_Dpip_dsh_write_str(sh, 0, dstr->str);\n         }\n      }\n      a_Dpip_dsh_write_str(sh, 0, modifypage_update_item_footer);\n   }\n\n   a_Dpip_dsh_write_str(sh, 1, modifypage_update_footer);\n\n   return 2;\n}\n\n/*\n * Make the modify-page and send it back\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int Bmsrv_send_modify_answer(Dsh *sh, char *url)\n{\n   char *d_cmd;\n   int st;\n\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n   st = a_Dpip_dsh_write_str(sh, 1, d_cmd);\n   dFree(d_cmd);\n   if (st != 0)\n      return 1;\n\n   /* Send HTTP header */\n   if (a_Dpip_dsh_write_str(sh, 0, Header) != 0) {\n      return 1;\n   }\n\n   if (MODIFY_PAGE_NUM == 2) {\n      MODIFY_PAGE_NUM = 1;\n      return Bmsrv_send_modify_page_add_section(sh);\n   } else if (MODIFY_PAGE_NUM == 3) {\n      MODIFY_PAGE_NUM = 1;\n      return Bmsrv_send_modify_update(sh, NULL);\n   } else if (MODIFY_PAGE_NUM == 4) {\n      MODIFY_PAGE_NUM = 1;\n      return Bmsrv_send_modify_page_add_url(sh);\n   } else {\n      return Bmsrv_send_modify_page(sh);\n   }\n}\n\n\n/* Operations */\n\n/*\n * Parse a delete bms request, delete them, and update bm file.\n * Return code: { 0:OK, 1:Abort }\n */\nstatic int Bmsrv_modify_delete(char *url)\n{\n   char *p;\n   int nb, ns, key;\n\n   /* bookmarks were loaded before */\n\n   /* Remove marked sections */\n   p = strchr(url, '?');\n   for (ns = 0; (p = strstr(p, \"&s\")); ++p) {\n      if (isdigit(p[2])) {\n         key = strtol(p + 2, NULL, 10);\n         Bms_sec_del(key);\n         ++ns;\n      }\n   }\n\n   /* Remove marked urls */\n   p = strchr(url, '?');\n   for (nb = 0; (p = strstr(p, \"&url\")); ++p) {\n      if (isdigit(p[4])) {\n         key = strtol(p + 4, NULL, 10);\n         Bms_del(key);\n         ++nb;\n      }\n   }\n\n/* -- This doesn't work because dillo erases the message upon the\n *    receipt of the first data stream.\n *\n   sprintf(msg, \"Deleted %d bookmark%s!>\", n, (n > 1) ? \"s\" : \"\");\n   if (Bmsrv_dpi_send_status_msg(sh, msg))\n      return 1;\n*/\n\n   /* Write new bookmarks file */\n   if (nb || ns)\n      Bms_save();\n\n   return 0;\n}\n\n/*\n * Parse a move urls request, move and update bm file.\n * Return code: { 0:OK, 1:Abort }\n */\nstatic int Bmsrv_modify_move(char *url)\n{\n   char *p;\n   int n, section = 0, key;\n\n   /* bookmarks were loaded before */\n\n   /* get target section */\n   for (p = url; (p = strstr(p, \"&s\")); ++p) {\n      if (isdigit(p[2])) {\n         section = strtol(p + 2, NULL, 10);\n         break;\n      }\n   }\n   if (!p)\n      return 1;\n\n   /* move marked urls */\n   p = strchr(url, '?');\n   for (n = 0; (p = strstr(p, \"&url\")); ++p) {\n      if (isdigit(p[4])) {\n         key = strtol(p + 4, NULL, 10);\n         Bms_move(key, section);\n         ++n;\n      }\n   }\n\n   /* Write new bookmarks file */\n   if (n) {\n      Bms_save();\n   }\n\n   return 0;\n}\n\n/*\n * Parse a modify request: update urls and sections, then save.\n * Return code: { 0:OK, 1:Abort }\n */\nstatic int Bmsrv_modify_update(char *url)\n{\n   char *p, *q, *title;\n   int i, key;\n\n   /* bookmarks were loaded before */\n\n   p = strchr(url, '?');\n   for (  ; (p = strstr(p, \"s\")); ++p) {\n      if (p[-1] == '&' || p[-1] == '?' ) {\n         for (i = 0; isdigit(p[1 + i]); ++i);\n         if (i && p[1 + i] == '=') {\n            /* we have a title/key to change */\n            key = strtol(p + 1, NULL, 10);\n            if ((q = strchr(p + 1, '&')))\n               title = dStrndup(p + 2 + i, (uint_t)(q - (p + 2 + i)));\n            else\n               title = dStrdup(p + 2 + i);\n\n            Unencode_str(title);\n            Bms_update_sec_title(key, title);\n            dFree(title);\n         }\n      }\n   }\n\n   p = strchr(url, '?');\n   for (  ; (p = strstr(p, \"title\")); ++p) {\n      if (p[-1] == '&' || p[-1] == '?' ) {\n         for (i = 0; isdigit(p[5 + i]); ++i);\n         if (i && p[5 + i] == '=') {\n            /* we have a title/key to change */\n            key = strtol(p + 5, NULL, 10);\n            if ((q = strchr(p + 5, '&')))\n               title = dStrndup(p + 6 + i, (uint_t)(q - (p + 6 + i)));\n            else\n               title = dStrdup(p + 6 + i);\n\n            Unencode_str(title);\n            Bms_update_title(key, title);\n            dFree(title);\n         }\n      }\n   }\n\n   /* Write new bookmarks file */\n   Bms_save();\n\n   return 0;\n}\n\n/*\n * Parse an \"add section\" request, and update the bm file.\n * Return code: { 0:OK, 1:Abort }\n */\nstatic int Bmsrv_modify_add_section(char *url)\n{\n   char *p, *title = NULL;\n\n   /* bookmarks were loaded before */\n\n   /* get new section's title */\n   if ((p = strstr(url, \"&title=\"))) {\n      title = dStrdup (p + 7);\n      if ((p = strchr(title, '&')))\n         *p = 0;\n      Unencode_str(title);\n   } else\n      return 1;\n\n   Bms_sec_add(title);\n   dFree(title);\n\n   /* Write new bookmarks file */\n   Bms_save();\n\n   return 0;\n}\n\n/*\n * Parse an \"add url\" request, and update the bm file.\n * Return code: { 0:OK, 1:Abort }\n */\nstatic int Bmsrv_modify_add_url(Dsh *sh, char *s_url)\n{\n   char *p, *q, *title, *u_title, *url;\n   int i;\n   static int section = 0;\n\n   /* bookmarks were loaded before */\n\n   if (sh == NULL) {\n      /* look for section */\n      for (q = s_url; (q = strstr(q, \"&s\")); ++q) {\n         for (i = 0; isdigit(q[2+i]); ++i);\n         if (q[2+i] == '=')\n            section = strtol(q + 2, NULL, 10);\n      }\n      return 1;\n   }\n\n   if (!(p = strstr(s_url, \"&title=\")) ||\n       !(q = strstr(s_url, \"&url=\")))\n      return 1;\n\n   title = dStrdup (p + 7);\n   if ((p = strchr(title, '&')))\n      *p = 0;\n   url = dStrdup (q + 5);\n   if ((p = strchr(url, '&')))\n      *p = 0;\n   if (strlen(title) && strlen(url)) {\n      Unencode_str(title);\n      Unencode_str(url);\n      u_title = Unescape_html_str(title);\n      Bms_add(section, url, u_title);\n      dFree(u_title);\n   }\n   dFree(title);\n   dFree(url);\n   section = 0;\n\n   /* TODO: we should send an \"Bookmark added\" message, but the\n      msg-after-HTML functionallity is still pending, not hard though. */\n\n   /* Write new bookmarks file */\n   Bms_save();\n\n   return 0;\n}\n\n/*\n * Check the parameters of a modify request, and return an error message\n * when it's wrong.\n * Return code: { 0:OK, 2:Close }\n */\nstatic int Bmsrv_check_modify_request(Dsh *sh, char *url)\n{\n   char *p, *msg;\n   int n_sec, n_url;\n\n   /* Count number of marked urls and sections */\n   Bmsrv_count_urls_and_sections(url, &n_sec, &n_url);\n\n   p = strchr(url, '?');\n   if (strstr(p, \"operation=delete&\")) {\n      if (n_url || n_sec)\n         return 0;\n      msg = \"Delete: you must mark what to delete!\";\n\n   } else if (strstr(url, \"operation=move&\")) {\n      if (n_url && n_sec)\n         return 0;\n      else if (n_url)\n         msg = \"Move: you must mark a target section!\";\n      else if (n_sec)\n         msg = \"Move: can not move a section (yet).\";\n      else\n         msg = \"Move: you must mark some urls, and a target section!\";\n\n   } else if (strstr(url, \"operation=modify&\")) {\n      if (n_url || n_sec)\n         return 0;\n      msg = \"Modify: you must mark what to update!\";\n\n   } else if (strstr(url, \"operation=modify2&\")) {\n      /* nothing to check here */\n      return 0;\n\n   } else if (strstr(url, \"operation=add_sec&\")) {\n      /* nothing to check here */\n      return 0;\n\n   } else if (strstr(url, \"operation=add_section&\")) {\n      /* nothing to check here */\n      return 0;\n\n   } else if (strstr(url, \"operation=add_url&\")) {\n      if (n_sec <= 1)\n         return 0;\n      msg = \"Add url: only one target section is allowed!\";\n\n   } else if (strstr(url, \"operation=add_url2&\")) {\n      /* nothing to check here */\n      return 0;\n\n   } else if (strstr(url, \"operation=none&\")) {\n      msg = \"No operation, just do nothing!\";\n\n   } else {\n      msg = \"Sorry, not implemented yet.\";\n   }\n\n   Bmsrv_dpi_send_status_msg(sh, msg);\n   return 2;\n}\n\n/*\n * Parse a and process a modify request.\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int Bmsrv_process_modify_request(Dsh *sh, char *url)\n{\n   /* check the provided parameters */\n   if (Bmsrv_check_modify_request(sh, url) != 0)\n      return 2;\n\n   if (strstr(url, \"operation=delete&\")) {\n      if (Bmsrv_modify_delete(url) == 1)\n         return 1;\n      if (Bmsrv_send_reload_request(sh, \"dpi:/bm/modify\") == 1)\n         return 1;\n\n   } else if (strstr(url, \"operation=move&\")) {\n      if (Bmsrv_modify_move(url) == 1)\n         return 1;\n      if (Bmsrv_send_reload_request(sh, \"dpi:/bm/modify\") == 1)\n         return 1;\n\n   } else if (strstr(url, \"operation=modify&\")) {\n      /* make a local copy of 'url' */\n      Bmsrv_send_modify_update(NULL, url);\n      MODIFY_PAGE_NUM = 3;\n      if (Bmsrv_send_reload_request(sh, \"dpi:/bm/modify\") == 1)\n         return 1;\n\n   } else if (strstr(url, \"operation=modify2&\")) {\n      if (Bmsrv_modify_update(url) == 1)\n         return 1;\n      if (Bmsrv_send_reload_request(sh, \"dpi:/bm/modify\") == 1)\n         return 1;\n\n   } else if (strstr(url, \"operation=add_sec&\")) {\n      /* this global variable tells which page to send  (--hackish...) */\n      MODIFY_PAGE_NUM = 2;\n      if (Bmsrv_send_reload_request(sh, \"dpi:/bm/modify\") == 1)\n         return 1;\n\n   } else if (strstr(url, \"operation=add_section&\")) {\n      if (Bmsrv_modify_add_section(url) == 1)\n         return 1;\n      if (Bmsrv_send_reload_request(sh, \"dpi:/bm/modify\") == 1)\n         return 1;\n\n   } else if (strstr(url, \"operation=add_url&\")) {\n      /* this global variable tells which page to send  (--hackish...) */\n      MODIFY_PAGE_NUM = 4;\n      /* parse section if present */\n      Bmsrv_modify_add_url(NULL, url);\n      if (Bmsrv_send_reload_request(sh, \"dpi:/bm/modify\") == 1)\n         return 1;\n\n   } else if (strstr(url, \"operation=add_url2&\")) {\n      if (Bmsrv_modify_add_url(sh, url) == 1)\n         return 1;\n      if (Bmsrv_send_reload_request(sh, \"dpi:/bm/modify\") == 1)\n         return 1;\n   }\n\n   return 2;\n}\n\n/* -- Bookmarks ------------------------------------------------------------ */\n\n/*\n * Send the current bookmarks page (in HTML)\n */\nstatic int send_bm_page(Dsh *sh)\n{\n   static Dstr *dstr = NULL;\n   char *l_title;\n   BmSec *sec_node;\n   BmRec *bm_node;\n   int i, j;\n\n   if (!dstr)\n      dstr = dStr_new(\"\");\n\n   if (a_Dpip_dsh_write_str(sh, 0, mainpage_header))\n      return 1;\n\n   /* write sections header */\n   if (a_Dpip_dsh_write_str(sh, 0, mainpage_sections_header))\n      return 1;\n   /* write sections */\n   for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) {\n      dStr_sprintf(dstr, mainpage_sections_item,\n                   sec_node->section, sec_node->title);\n      if (a_Dpip_dsh_write_str(sh, 0, dstr->str))\n         return 1;\n   }\n   /* write sections footer */\n   if (a_Dpip_dsh_write_str(sh, 0, mainpage_sections_footer))\n      return 1;\n\n   /* send page middle */\n   if (a_Dpip_dsh_write_str(sh, 0, mainpage_middle1))\n      return 1;\n\n   /* send bookmark cards */\n   for (i = 0; (sec_node = dList_nth_data(B_secs, i)); ++i) {\n      /* send card header */\n      l_title = make_one_line_str(sec_node->title);\n      dStr_sprintf(dstr, mainpage_section_card_header,\n                   sec_node->section, l_title);\n      dFree(l_title);\n      if (a_Dpip_dsh_write_str(sh, 0, dstr->str))\n         return 1;\n\n      /* send section's bookmarks */\n      for (j = 0; (bm_node = dList_nth_data(B_bms, j)); ++j) {\n         if (bm_node->section == sec_node->section) {\n            dStr_sprintf(dstr, mainpage_section_card_item,\n                         bm_node->url, bm_node->title);\n            if (a_Dpip_dsh_write_str(sh, 0, dstr->str))\n               return 1;\n         }\n      }\n\n      /* send card footer */\n      if (a_Dpip_dsh_write_str(sh, 0, mainpage_section_card_footer))\n         return 1;\n   }\n\n   /* finish page */\n   if (a_Dpip_dsh_write_str(sh, 1, mainpage_footer))\n      return 1;\n\n   return 0;\n}\n\n\n/* -- Dpi parser ----------------------------------------------------------- */\n\n/*\n * Parse a data stream (dpi protocol)\n * Note: Buf is a dpip token (zero terminated string)\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int Bmsrv_parse_token(Dsh *sh, char *Buf)\n{\n   static char *msg1=NULL, *msg2=NULL, *msg3=NULL;\n   char *cmd, *d_cmd, *url, *title, *msg;\n   size_t BufSize;\n   int st;\n\n   if (!msg1) {\n     /* Initialize data for the \"chat\" command. */\n     msg1 = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"chat\", \"Hi browser\");\n     msg2 = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"chat\", \"Is it worth?\");\n     msg3 = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"chat\", \"Ok, send it\");\n   }\n\n   if (sh->mode & DPIP_RAW) {\n      MSG(\"ERROR: Unhandled DPIP_RAW mode!\\n\");\n      return 1;\n   }\n\n   BufSize = strlen(Buf);\n   cmd = a_Dpip_get_attr_l(Buf, BufSize, \"cmd\");\n\n   if (cmd && strcmp(cmd, \"chat\") == 0) {\n      dFree(cmd);\n      msg = a_Dpip_get_attr_l(Buf, BufSize, \"msg\");\n      if (*msg == 'H') {\n         /* \"Hi server\" */\n         if (a_Dpip_dsh_write_str(sh, 1, msg1))\n            return 1;\n      } else if (*msg == 'I') {\n         /* \"I want to set abookmark\" */\n         if (a_Dpip_dsh_write_str(sh, 1, msg2))\n            return 1;\n      } else if (*msg == 'S') {\n         /* \"Sure\" */\n         if (a_Dpip_dsh_write_str(sh, 1, msg3))\n            return 1;\n      }\n      dFree(msg);\n      return 0;\n   }\n\n   /* sync with the bookmarks file */\n   Bms_cond_load();\n\n   if (cmd && strcmp(cmd, \"DpiBye\") == 0) {\n      MSG(\"(pid %d): Got DpiBye.\\n\", (int)getpid());\n      exit(0);\n\n   } else if (cmd && strcmp(cmd, \"add_bookmark\") == 0) {\n      dFree(cmd);\n      url = a_Dpip_get_attr_l(Buf, BufSize, \"url\");\n      title = a_Dpip_get_attr_l(Buf, BufSize, \"title\");\n      if (strlen(title) == 0) {\n         dFree(title);\n         title = dStrdup(\"(Untitled)\");\n      }\n      if (url && title)\n         Bmsrv_add_bm(sh, url, title);\n      dFree(url);\n      dFree(title);\n      return 2;\n\n   } else if (cmd && strcmp(cmd, \"open_url\") == 0) {\n      dFree(cmd);\n      url = a_Dpip_get_attr_l(Buf, BufSize, \"url\");\n\n      if (dStrnAsciiCasecmp(url, \"dpi:\", 4) == 0) {\n         if (strcmp(url+4, \"/bm/modify\") == 0) {\n            st = Bmsrv_send_modify_answer(sh, url);\n            dFree(url);\n            return st;\n         } else if (strncmp(url+4, \"/bm/modify?\", 11) == 0) {\n            /* process request */\n            st = Bmsrv_process_modify_request(sh, url);\n            dFree(url);\n            return st;\n         }\n      }\n\n\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n      dFree(url);\n      st = a_Dpip_dsh_write_str(sh, 1, d_cmd);\n      dFree(d_cmd);\n      if (st != 0)\n         return 1;\n\n      /* Send HTTP header */\n      if (a_Dpip_dsh_write_str(sh, 1, Header) != 0) {\n         return 1;\n      }\n\n      st = send_bm_page(sh);\n      if (st != 0) {\n         char *err =\n            DOCTYPE\n            \"<HTML><body id='dillo_bm'> Error on the bookmarks server...\"\n            \"      </body></html>\";\n         if (a_Dpip_dsh_write_str(sh, 1, err) != 0) {\n            return 1;\n         }\n      }\n      return 2;\n   }\n\n   return 0;\n}\n\n/* --  Termination handlers ----------------------------------------------- */\n/*\n * (was to delete the local namespace socket),\n *  but this is handled by 'dpid' now.\n */\nstatic void cleanup(void)\n{\n  /* no cleanup required */\n}\n\n/*\n * Perform any necessary cleanups upon abnormal termination\n */\nstatic void termination_handler(int signum)\n{\n  exit(signum);\n}\n\n\n/*\n * -- MAIN -------------------------------------------------------------------\n */\nint main(void) {\n   struct sockaddr_un spun;\n   int sock_fd, code;\n   socklen_t address_size;\n   char *tok;\n   Dsh *sh;\n\n   /* Arrange the cleanup function for terminations via exit() */\n   atexit(cleanup);\n\n   /* Arrange the cleanup function for abnormal terminations */\n   if (signal (SIGINT, termination_handler) == SIG_IGN)\n     signal (SIGINT, SIG_IGN);\n   if (signal (SIGHUP, termination_handler) == SIG_IGN)\n     signal (SIGHUP, SIG_IGN);\n   if (signal (SIGTERM, termination_handler) == SIG_IGN)\n     signal (SIGTERM, SIG_IGN);\n\n   /* We may receive SIGPIPE (e.g. socket is closed early by our client) */\n   signal(SIGPIPE, SIG_IGN);\n\n   /* Initialize local data */\n   B_bms = dList_new(512);\n   B_secs = dList_new(32);\n   BmFile = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/bm.txt\", NULL);\n   /* some OSes may need this... */\n   address_size = sizeof(struct sockaddr_un);\n\n   MSG(\"(v.13): accepting connections...\\n\");\n\n   while (1) {\n      sock_fd = accept(STDIN_FILENO, (struct sockaddr *)&spun, &address_size);\n      if (sock_fd == -1) {\n         perror(\"[accept]\");\n         exit(1);\n      }\n\n      /* create the Dsh structure */\n      sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);\n\n      /* Authenticate our client... */\n      if (!(tok = a_Dpip_dsh_read_token(sh, 1)) ||\n          a_Dpip_check_auth(tok) < 0) {\n         MSG(\"can't authenticate request: %s\\n\", dStrerror(errno));\n         a_Dpip_dsh_close(sh);\n         exit(1);\n      }\n      dFree(tok);\n\n      while (1) {\n         code = 1;\n         if ((tok = a_Dpip_dsh_read_token(sh, 1)) != NULL) {\n            /* Let's see what we fished... */\n            code = Bmsrv_parse_token(sh, tok);\n         }\n         dFree(tok);\n\n         if (code != 0) {\n            /* socket is not operative (e.g. closed by client) */\n            break;\n         }\n      }\n\n      a_Dpip_dsh_close(sh);\n      a_Dpip_dsh_free(sh);\n\n   }/*while*/\n}\n"
  },
  {
    "path": "dpi/cookies.c",
    "content": "/*\n * File: cookies.c\n * Cookies server.\n *\n * Copyright 2001 Lars Clausen   <lrclause@cs.uiuc.edu>\n *                Jrgen Viksell <jorgen.viksell@telia.com>\n * Copyright 2002-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* The current standard for cookies is RFC 6265.\n *\n * Info from 2009 on cookies in the wild:\n *  http://www.ietf.org/mail-archive/web/http-state/current/msg00078.html\n * And dates specifically:\n *  http://www.ietf.org/mail-archive/web/http-state/current/msg00128.html\n */\n\n#ifdef DISABLE_COOKIES\n\nint main(void)\n{\n   return 0; /* never called */\n}\n\n#else\n\n\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/un.h>\n#include <netinet/in.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <errno.h>\n#include <stddef.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <time.h>       /* for time() and time_t */\n#include <ctype.h>\n#include <limits.h>\n#include <netdb.h>\n#include <signal.h>\n#include \"dpiutil.h\"\n#include \"../dpip/dpip.h\"\n\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[cookies dpi]: \" __VA_ARGS__)\n\n/*\n * a_List_add()\n *\n * Make sure there's space for 'num_items' items within the list\n * (First, allocate an 'alloc_step' sized chunk, after that, double the\n *  list size --to make it faster)\n */\n#define a_List_add(list,num_items,alloc_step) \\\n   if (!list) { \\\n      list = dMalloc(alloc_step * sizeof((*list))); \\\n   } \\\n   if (num_items >= alloc_step){ \\\n      while ( num_items >= alloc_step ) \\\n         alloc_step <<= 1; \\\n      list = dRealloc(list, alloc_step * sizeof((*list))); \\\n   }\n\n/* The maximum length of a line in the cookie file */\n#define LINE_MAXLEN 4096\n\n#define MAX_DOMAIN_COOKIES 20\n#define MAX_TOTAL_COOKIES 1200\n\ntypedef enum {\n   COOKIE_ACCEPT,\n   COOKIE_ACCEPT_SESSION,\n   COOKIE_DENY\n} CookieControlAction;\n\ntypedef struct {\n   char *domain;\n   CookieControlAction action;\n} CookieControl;\n\ntypedef struct {\n   char *domain;\n   Dlist *cookies;\n} DomainNode;\n\ntypedef struct {\n   char *name;\n   char *value;\n   char *domain;\n   char *path;\n   time_t expires_at;\n   bool_t host_only;\n   bool_t secure;\n   bool_t session_only;\n   long last_used;\n} CookieData_t;\n\ntypedef struct {\n   Dsh *sh;\n   int status;\n} ClientInfo;\n\n/*\n * Local data\n */\n\nstatic Dlist *all_cookies;\n\n/* List of DomainNode. Each node holds a domain and its list of cookies */\nstatic Dlist *domains;\n\n/* Variables for access control */\nstatic CookieControl *ccontrol = NULL;\nstatic int num_ccontrol = 0;\nstatic int num_ccontrol_max = 1;\nstatic CookieControlAction default_action = COOKIE_DENY;\n\nstatic long cookies_use_counter = 0;\nstatic bool_t disabled;\nstatic FILE *file_stream;\nstatic const char *const cookies_txt_header_str =\n\"# HTTP Cookie File\\n\"\n\"# This is a generated file!  Do not edit.\\n\"\n\"# [domain  subdomains  path  secure  expiry_time  name  value]\\n\\n\";\n\n/* The epoch is Jan 1, 1970. When there is difficulty in representing future\n * dates, use the (by far) most likely last representable time in Jan 19, 2038.\n */\nstatic struct tm cookies_epoch_tm = {0, 0, 0, 1, 0, 70, 0, 0, 0, 0, 0};\nstatic time_t cookies_epoch_time, cookies_future_time;\n\n/*\n * Forward declarations\n */\n\nstatic CookieControlAction Cookies_control_check_domain(const char *domain);\nstatic int Cookie_control_init(void);\nstatic void Cookies_add_cookie(CookieData_t *cookie);\nstatic int Cookies_cmp(const void *a, const void *b);\n\n/*\n * Compare function for searching a domain node\n */\nstatic int Domain_node_cmp(const void *v1, const void *v2)\n{\n   const DomainNode *n1 = v1, *n2 = v2;\n\n   return dStrAsciiCasecmp(n1->domain, n2->domain);\n}\n\n/*\n * Compare function for searching a domain node by domain\n */\nstatic int Domain_node_by_domain_cmp(const void *v1, const void *v2)\n{\n   const DomainNode *node = v1;\n   const char *domain = v2;\n\n   return dStrAsciiCasecmp(node->domain, domain);\n}\n\n/*\n * Delete node. This will not free any cookies that might be in node->cookies.\n */\nstatic void Cookies_delete_node(DomainNode *node)\n{\n   dList_remove(domains, node);\n   dFree(node->domain);\n   dList_free(node->cookies);\n   dFree(node);\n}\n\n/*\n * Return a file pointer. If the file doesn't exist, try to create it,\n * with the optional 'init_str' as its content.\n */\nstatic FILE *Cookies_fopen(const char *filename, const char *mode,\n                           const char *init_str)\n{\n   FILE *F_in;\n   int fd, rc;\n\n   if ((F_in = fopen(filename, mode)) == NULL) {\n      /* Create the file */\n      fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);\n      if (fd != -1) {\n         if (init_str) {\n            rc = write(fd, init_str, strlen(init_str));\n            if (rc == -1) {\n               MSG(\"Cookies: Could not write initial string to file %s: %s\\n\",\n                  filename, dStrerror(errno));\n            }\n         }\n         close(fd);\n\n         MSG(\"Created file: %s\\n\", filename);\n         F_in = fopen(filename, mode);\n      } else {\n         MSG(\"Could not create file: %s!\\n\", filename);\n      }\n   }\n\n   if (F_in) {\n      /* set close on exec */\n      fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD));\n   }\n\n   return F_in;\n}\n\nstatic void Cookies_free_cookie(CookieData_t *cookie)\n{\n   dFree(cookie->name);\n   dFree(cookie->value);\n   dFree(cookie->domain);\n   dFree(cookie->path);\n   dFree(cookie);\n}\n\nstatic void Cookies_tm_init(struct tm *tm)\n{\n   tm->tm_sec = cookies_epoch_tm.tm_sec;\n   tm->tm_min = cookies_epoch_tm.tm_min;\n   tm->tm_hour = cookies_epoch_tm.tm_hour;\n   tm->tm_mday = cookies_epoch_tm.tm_mday;\n   tm->tm_mon = cookies_epoch_tm.tm_mon;\n   tm->tm_year = cookies_epoch_tm.tm_year;\n   tm->tm_isdst = cookies_epoch_tm.tm_isdst;\n}\n\n/*\n * Read in cookies from 'stream' (cookies.txt)\n */\nstatic void Cookies_load_cookies(FILE *stream)\n{\n   char line[LINE_MAXLEN];\n\n   all_cookies = dList_new(32);\n   domains = dList_new(32);\n\n   /* Get all lines in the file */\n   while (!feof(stream)) {\n      line[0] = '\\0';\n      if ((fgets(line, LINE_MAXLEN, stream) == NULL) && ferror(stream)) {\n         MSG(\"Error while reading from cookies.txt: %s\\n\", dStrerror(errno));\n         break; /* bail out */\n      }\n\n      /* Remove leading and trailing whitespaces */\n      dStrstrip(line);\n\n      if ((line[0] != '\\0') && (line[0] != '#')) {\n         /*\n          * Split the row into pieces using a tab as the delimiter.\n          * pieces[0] The domain name\n          * pieces[1] TRUE/FALSE: is the domain a suffix, or a full domain?\n          * pieces[2] The path\n          * pieces[3] TRUE/FALSE: is the cookie for secure use only?\n          * pieces[4] Timestamp of expire date\n          * pieces[5] Name of the cookie\n          * pieces[6] Value of the cookie\n          */\n         CookieControlAction action;\n         char *piece;\n         char *line_marker = line;\n         CookieData_t *cookie = dNew0(CookieData_t, 1);\n\n         cookie->session_only = FALSE;\n         cookie->domain = dStrdup(dStrsep(&line_marker, \"\\t\"));\n         piece = dStrsep(&line_marker, \"\\t\");\n         if (piece != NULL && piece[0] == 'F')\n            cookie->host_only = TRUE;\n         cookie->path = dStrdup(dStrsep(&line_marker, \"\\t\"));\n         piece = dStrsep(&line_marker, \"\\t\");\n         if (piece != NULL && piece[0] == 'T')\n            cookie->secure = TRUE;\n         piece = dStrsep(&line_marker, \"\\t\");\n         if (piece != NULL) {\n            /* There is some problem with simply putting the maximum value\n             * into tm.tm_sec (although a value close to it works).\n             */\n            long seconds = strtol(piece, NULL, 10);\n            struct tm tm;\n            Cookies_tm_init(&tm);\n            tm.tm_min += seconds / 60;\n            tm.tm_sec += seconds % 60;\n            cookie->expires_at = mktime(&tm);\n         } else {\n            cookie->expires_at = (time_t) -1;\n         }\n         cookie->name = dStrdup(dStrsep(&line_marker, \"\\t\"));\n         cookie->value = dStrdup(line_marker ? line_marker : \"\");\n\n         if (!cookie->domain || cookie->domain[0] == '\\0' ||\n             !cookie->path || cookie->path[0] != '/' ||\n             !cookie->name || !cookie->value) {\n            MSG(\"Malformed line in cookies.txt file!\\n\");\n            Cookies_free_cookie(cookie);\n            continue;\n         }\n\n         action = Cookies_control_check_domain(cookie->domain);\n         if (action == COOKIE_DENY) {\n            Cookies_free_cookie(cookie);\n            continue;\n         } else if (action == COOKIE_ACCEPT_SESSION) {\n            cookie->session_only = TRUE;\n         }\n\n         /* Save cookie in memory */\n         Cookies_add_cookie(cookie);\n      }\n   }\n   MSG(\"Cookies loaded: %d.\\n\", dList_length(all_cookies));\n}\n\n/*\n * Initialize the cookies module\n * (The 'disabled' variable is writeable only within Cookies_init)\n */\nstatic void Cookies_init()\n{\n   char *filename;\n#ifndef HAVE_LOCKF\n   struct flock lck;\n#endif\n   struct tm future_tm = {7, 14, 3, 19, 0, 138, 0, 0, 0, 0, 0};\n\n   /* Default setting */\n   disabled = TRUE;\n\n   cookies_epoch_time = mktime(&cookies_epoch_tm);\n   cookies_future_time = mktime(&future_tm);\n\n   /* Read and parse the cookie control file (cookiesrc) */\n   if (Cookie_control_init() != 0) {\n      MSG(\"Disabling cookies.\\n\");\n      return;\n   }\n\n   /* Get a stream for the cookies file */\n   filename = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/cookies.txt\", NULL);\n   file_stream = Cookies_fopen(filename, \"r+\", cookies_txt_header_str);\n\n   dFree(filename);\n\n   if (!file_stream) {\n      MSG(\"ERROR: Can't open ~/.\" BINNAME \"/cookies.txt; disabling cookies\\n\");\n      return;\n   }\n\n   /* Try to get a lock from the file descriptor */\n#ifdef HAVE_LOCKF\n   disabled = (lockf(fileno(file_stream), F_TLOCK, 0) == -1);\n#else /* POSIX lock */\n   lck.l_start = 0; /* start at beginning of file */\n   lck.l_len = 0;  /* lock entire file */\n   lck.l_type = F_WRLCK;\n   lck.l_whence = SEEK_SET;  /* absolute offset */\n\n   disabled = (fcntl(fileno(file_stream), F_SETLK, &lck) == -1);\n#endif\n   if (disabled) {\n      MSG(\"The cookies file has a file lock; disabling cookies!\\n\");\n      fclose(file_stream);\n      return;\n   }\n   MSG(\"Enabling cookies as per cookiesrc...\\n\");\n\n   Cookies_load_cookies(file_stream);\n}\n\n/*\n * Flush cookies to disk and free all the memory allocated.\n */\nstatic void Cookies_save_and_free()\n{\n   int i, fd, saved = 0;\n   DomainNode *node;\n   CookieData_t *cookie;\n   time_t now;\n\n#ifndef HAVE_LOCKF\n   struct flock lck;\n#endif\n\n   if (disabled)\n      return;\n\n   now = time(NULL);\n\n   rewind(file_stream);\n   fd = fileno(file_stream);\n   if (ftruncate(fd, 0) == -1)\n      MSG(\"Cookies: Truncate file stream failed: %s\\n\", dStrerror(errno));\n   fprintf(file_stream, \"%s\", cookies_txt_header_str);\n\n   /* Iterate cookies per domain, saving and freeing */\n   while ((node = dList_nth_data(domains, 0))) {\n      for (i = 0; (cookie = dList_nth_data(node->cookies, i)); ++i) {\n         if (!cookie->session_only && difftime(cookie->expires_at, now) > 0) {\n            int len;\n            char buf[LINE_MAXLEN];\n\n            len = snprintf(buf, LINE_MAXLEN, \"%s\\t%s\\t%s\\t%s\\t%ld\\t%s\\t%s\\n\",\n                           cookie->domain,\n                           cookie->host_only ? \"FALSE\" : \"TRUE\",\n                           cookie->path,\n                           cookie->secure ? \"TRUE\" : \"FALSE\",\n                           (long) difftime(cookie->expires_at,\n                                           cookies_epoch_time),\n                           cookie->name,\n                           cookie->value);\n            if (len < LINE_MAXLEN) {\n               fprintf(file_stream, \"%s\", buf);\n               saved++;\n            } else {\n               MSG(\"Not saving overly long cookie for %s.\\n\", cookie->domain);\n            }\n         }\n         Cookies_free_cookie(cookie);\n      }\n      Cookies_delete_node(node);\n   }\n   dList_free(domains);\n   dList_free(all_cookies);\n\n#ifdef HAVE_LOCKF\n   lockf(fd, F_ULOCK, 0);\n#else  /* POSIX file lock */\n   lck.l_start = 0; /* start at beginning of file */\n   lck.l_len = 0;  /* lock entire file */\n   lck.l_type = F_UNLCK;\n   lck.l_whence = SEEK_SET;  /* absolute offset */\n\n   fcntl(fileno(file_stream), F_SETLKW, &lck);\n#endif\n   fclose(file_stream);\n\n   MSG(\"Cookies saved: %d.\\n\", saved);\n}\n\n/*\n * Month parsing\n */\nstatic bool_t Cookies_get_month(struct tm *tm, const char **str)\n{\n   static const char *const months[] =\n   { \"Jan\", \"Feb\", \"Mar\",\n     \"Apr\", \"May\", \"Jun\",\n     \"Jul\", \"Aug\", \"Sep\",\n     \"Oct\", \"Nov\", \"Dec\"\n   };\n   int i;\n\n   for (i = 0; i < 12; i++) {\n      if (!dStrnAsciiCasecmp(months[i], *str, 3)) {\n         _MSG(\"Found month: %s\\n\", months[i]);\n         tm->tm_mon = i;\n         *str += 3; \n         return TRUE;\n      }\n   }\n   return FALSE;\n}\n\n/*\n * As seen in the production below, it's just one digit or two.\n * Return the value, or -1 if no proper value found.\n */\nstatic int Cookies_get_timefield(const char **str)\n{\n   int n;\n   const char *s = *str;\n\n   if (!isdigit(*s))\n      return -1;\n\n   n = *(s++) - '0';\n   if (isdigit(*s)) {\n      n *= 10;\n      n += *(s++) - '0';\n      if (isdigit(*s))\n         return -1;\n   }\n   *str = s;\n   return n;\n}\n\n/*\n * Time parsing: 'time-field \":\" time-field \":\" time-field'\n *               'time-field = 1*2DIGIT'\n */\nstatic bool_t Cookies_get_time(struct tm *tm, const char **str)\n{\n   const char *s = *str;\n\n   if ((tm->tm_hour = Cookies_get_timefield(&s)) == -1)\n      return FALSE;\n\n   if (*(s++) != ':')\n      return FALSE;\n\n   if ((tm->tm_min = Cookies_get_timefield(&s)) == -1)\n      return FALSE;\n\n   if (*(s++) != ':')\n      return FALSE;\n\n   if ((tm->tm_sec = Cookies_get_timefield(&s)) == -1)\n      return FALSE;\n\n   *str = s;\n   return TRUE;\n}\n\n/*\n * Day parsing: \"day-of-month    = 1*2DIGIT\"\n */\nstatic bool_t Cookies_get_day(struct tm *tm, const char **str)\n{\n   const char *s = *str;\n\n   if ((tm->tm_mday = Cookies_get_timefield(&s)) == -1)\n      return FALSE;\n\n   *str = s;\n   return TRUE;\n}\n\n/*\n * Date parsing: \"year = 2*4DIGIT\"\n */\nstatic bool_t Cookies_get_year(struct tm *tm, const char **str)\n{\n   int n;\n   const char *s = *str;\n\n   if (isdigit(*s))\n      n = *(s++) - '0';\n   else\n      return FALSE;\n   if (isdigit(*s)) {\n      n *= 10;\n      n += *(s++) - '0';\n   } else\n      return FALSE;\n   if (isdigit(*s)) {\n      n *= 10;\n      n += *(s++) - '0';\n   }\n   if (isdigit(*s)) {\n      n *= 10;\n      n += *(s++) - '0';\n   }\n   if (isdigit(*s)) {\n      /* Sorry, users of prehistoric software in the year 10000! */\n      return FALSE;\n   }\n   if (n >= 70 && n <= 99)\n      n += 1900;\n   else if (n <= 69)\n      n += 2000;\n\n   tm->tm_year = n - 1900;\n\n   *str = s;\n   return TRUE;\n}\n\n/*\n * As given in RFC 6265.\n */\nstatic bool_t Cookies_date_delim(char c)\n{\n   return (c == '\\x09' ||\n           (c >= '\\x20' && c <= '\\x2F') ||\n           (c >= '\\x3B' && c <= '\\x40') ||\n           (c >= '\\x5B' && c <= '\\x60') ||\n           (c >= '\\x7B' && c <= '\\x7E'));\n}\n\n/*\n * Parse date string.\n *\n * A true nightmare of date formats appear in cookies, so one basically\n * has to paw through the soup and look for anything that looks sufficiently\n * like any of the date fields.\n *\n * Return a pointer to a struct tm, or NULL on error.\n */\nstatic struct tm *Cookies_parse_date(const char *date)\n{\n   bool_t found_time = FALSE, found_day = FALSE, found_month = FALSE,\n          found_year = FALSE, matched;\n   struct tm *tm = dNew0(struct tm, 1);\n   const char *s = date;\n\n   while (*s) {\n      matched = FALSE;\n\n      if (!found_time)\n         matched = found_time = Cookies_get_time(tm, &s);\n      if (!matched && !found_day)\n         matched = found_day = Cookies_get_day(tm, &s);\n      if (!matched && !found_month)\n         matched = found_month = Cookies_get_month(tm, &s);\n      if (!matched && !found_year)\n         matched = found_year = Cookies_get_year(tm, &s);\n      while (*s && !Cookies_date_delim(*s))\n         s++;\n      while (*s && Cookies_date_delim(*s))\n         s++;\n   }\n   if (!found_time || !found_day || !found_month || !found_year) {\n      dFree(tm);\n      tm = NULL;\n      MSG(\"In date \\\"%s\\\", format not understood.\\n\", date);\n   }\n\n   /* Error checks. This may be overkill.\n    *\n    * RFC 6265: \"Note that leap seconds cannot be represented in this\n    * syntax.\" I'm not sure whether that's good, but that's what it says.\n    */\n   if (tm &&\n       !(tm->tm_mday > 0 && tm->tm_mday < 32 && tm->tm_mon >= 0 &&\n         tm->tm_mon < 12 && tm->tm_year >= 0 && tm->tm_hour >= 0 &&\n         tm->tm_hour < 24 && tm->tm_min >= 0 && tm->tm_min < 60 &&\n         tm->tm_sec >= 0 && tm->tm_sec < 60)) {\n      MSG(\"Date \\\"%s\\\" values not in range.\\n\", date);\n      dFree(tm);\n      tm = NULL;\n   }\n\n   return tm;\n}\n\n/*\n * Find the least recently used cookie among those in the provided list.\n */\nstatic CookieData_t *Cookies_get_LRU(Dlist *cookies)\n{\n   int i, n = dList_length(cookies);\n   CookieData_t *lru = dList_nth_data(cookies, 0);\n\n   for (i = 1; i < n; i++) {\n      CookieData_t *curr = dList_nth_data(cookies, i);\n\n      if (curr->last_used < lru->last_used)\n         lru = curr;\n   }\n   return lru;\n}\n\n/*\n * Delete expired cookies.\n * If node is given, only check those cookies.\n * Note that nodes can disappear if all of their cookies were expired.\n *\n * Return the number of cookies that were expired.\n */\nstatic int Cookies_rm_expired_cookies(DomainNode *node)\n{\n   Dlist *cookies = node ? node->cookies : all_cookies;\n   int removed = 0;\n   int i = 0, n = dList_length(cookies);\n   time_t now = time(NULL);\n\n   while (i < n) {\n      CookieData_t *c = dList_nth_data(cookies, i);\n\n      if (difftime(c->expires_at, now) < 0) {\n         DomainNode *currnode = node ? node :\n              dList_find_sorted(domains, c->domain, Domain_node_by_domain_cmp);\n         dList_remove(currnode->cookies, c);\n         if (dList_length(currnode->cookies) == 0)\n            Cookies_delete_node(currnode);\n         dList_remove_fast(all_cookies, c);\n         Cookies_free_cookie(c);\n         n--;\n         removed++;\n      } else {\n         i++;\n      }\n   }\n   return removed;\n}\n\n/*\n * There are too many cookies. Choose one to remove and delete.\n * If node is given, select from among its cookies only.\n */\nstatic void Cookies_too_many(DomainNode *node)\n{\n   CookieData_t *lru = Cookies_get_LRU(node ? node->cookies : all_cookies);\n\n   MSG(\"Too many cookies! \"\n       \"Removing LRU cookie for \\'%s\\': \\'%s=%s\\'\\n\", lru->domain,\n       lru->name, lru->value);\n   if (!node)\n      node = dList_find_sorted(domains, lru->domain,Domain_node_by_domain_cmp);\n\n   dList_remove(node->cookies, lru);\n   dList_remove_fast(all_cookies, lru);\n   Cookies_free_cookie(lru);\n   if (dList_length(node->cookies) == 0)\n      Cookies_delete_node(node);\n}\n\nstatic void Cookies_add_cookie(CookieData_t *cookie)\n{\n   Dlist *domain_cookies;\n   CookieData_t *c;\n   DomainNode *node;\n\n   node = dList_find_sorted(domains, cookie->domain,Domain_node_by_domain_cmp);\n   domain_cookies = (node) ? node->cookies : NULL;\n\n   if (domain_cookies) {\n      /* Remove any cookies with the same name, path, and host-only values. */\n      while ((c = dList_find_custom(domain_cookies, cookie, Cookies_cmp))) {\n         dList_remove(domain_cookies, c);\n         dList_remove_fast(all_cookies, c);\n         Cookies_free_cookie(c);\n      }\n   }\n\n   if ((cookie->expires_at == (time_t) -1) ||\n       (difftime(cookie->expires_at, time(NULL)) <= 0)) {\n      /*\n       * Don't add an expired cookie. Whether expiring now == expired, exactly,\n       * is arguable, but we definitely do not want to add a Max-Age=0 cookie.\n       */\n      _MSG(\"Goodbye, cookie %s=%s d:%s p:%s\\n\", cookie->name,\n           cookie->value, cookie->domain, cookie->path);\n      Cookies_free_cookie(cookie);\n   } else {\n      if (domain_cookies && dList_length(domain_cookies) >=MAX_DOMAIN_COOKIES){\n         int removed = Cookies_rm_expired_cookies(node);\n\n         if (removed == 0) {\n            Cookies_too_many(node);\n         } else if (removed >= MAX_DOMAIN_COOKIES) {\n            /* So many were removed that the node might have been deleted. */\n            node = dList_find_sorted(domains, cookie->domain,\n                                                    Domain_node_by_domain_cmp);\n            domain_cookies = (node) ? node->cookies : NULL;\n         }\n      }\n      if (dList_length(all_cookies) >= MAX_TOTAL_COOKIES) {\n         if (Cookies_rm_expired_cookies(NULL) == 0) {\n            Cookies_too_many(NULL);\n         } else if (domain_cookies) {\n            /* Our own node might have just been deleted. */\n            node = dList_find_sorted(domains, cookie->domain,\n                                                    Domain_node_by_domain_cmp);\n            domain_cookies = (node) ? node->cookies : NULL;\n         }\n      }\n\n      cookie->last_used = cookies_use_counter++;\n\n      /* Actually add the cookie! */\n      dList_append(all_cookies, cookie);\n\n      if (!domain_cookies) {\n         domain_cookies = dList_new(5);\n         dList_append(domain_cookies, cookie);\n         node = dNew(DomainNode, 1);\n         node->domain = dStrdup(cookie->domain);\n         node->cookies = domain_cookies;\n         dList_insert_sorted(domains, node, Domain_node_cmp);\n      } else {\n         dList_append(domain_cookies, cookie);\n      }\n   }\n   if (domain_cookies && (dList_length(domain_cookies) == 0))\n      Cookies_delete_node(node);\n}\n\n/*\n * Return the attribute that is present at *cookie_str.\n */\nstatic char *Cookies_parse_attr(char **cookie_str)\n{\n   char *str;\n   uint_t len;\n\n   while (dIsspace(**cookie_str))\n      (*cookie_str)++;\n\n   str = *cookie_str;\n   /* find '=' at end of attr, ';' after attr/val pair, '\\0' end of string */\n   len = strcspn(str, \"=;\");\n   *cookie_str += len;\n\n   while (len && (str[len - 1] == ' ' || str[len - 1] == '\\t'))\n      len--;\n   return dStrndup(str, len);\n}\n\n/*\n * Get the value in *cookie_str.\n */\nstatic char *Cookies_parse_value(char **cookie_str)\n{\n   uint_t len;\n   char *str;\n\n   if (**cookie_str == '=') {\n      (*cookie_str)++;\n      while (dIsspace(**cookie_str))\n         (*cookie_str)++;\n\n      str = *cookie_str;\n      /* finds ';' after attr/val pair or '\\0' at end of string */\n      len = strcspn(str, \";\");\n      *cookie_str += len;\n\n      while (len && (str[len - 1] == ' ' || str[len - 1] == '\\t'))\n         len--;\n   } else {\n      str = *cookie_str;\n      len = 0;\n   }\n   return dStrndup(str, len);\n}\n\n/*\n * Advance past any value\n */\nstatic void Cookies_eat_value(char **cookie_str)\n{\n   if (**cookie_str == '=')\n      *cookie_str += strcspn(*cookie_str, \";\");\n}\n\n/*\n * Return the number of seconds by which our clock is ahead of the server's\n * clock.\n */\nstatic double Cookies_server_timediff(const char *server_date)\n{\n   double ret = 0;\n\n   if (server_date) {\n      struct tm *server_tm = Cookies_parse_date(server_date);\n\n      if (server_tm) {\n         time_t server_time = mktime(server_tm);\n\n         if (server_time != (time_t) -1)\n            ret = difftime(time(NULL), server_time);\n         dFree(server_tm);\n      }\n   }\n   return ret;\n}\n\nstatic void Cookies_unquote_string(char *str)\n{\n   if (str && str[0] == '\\\"') {\n      uint_t len = strlen(str);\n\n      if (len > 1 && str[len - 1] == '\\\"') {\n         str[len - 1] = '\\0';\n         while ((*str = str[1]))\n            str++;\n      }\n   }\n}\n\n/*\n * Parse cookie. A cookie might look something like:\n * \"Name=Val; Domain=example.com; Max-Age=3600; HttpOnly\"\n */\nstatic CookieData_t *Cookies_parse(char *cookie_str, const char *server_date)\n{\n   CookieData_t *cookie = NULL;\n   char *str = cookie_str;\n   bool_t first_attr = TRUE;\n   bool_t max_age = FALSE;\n   bool_t expires = FALSE;\n\n   /* Iterate until there is nothing left of the string */\n   while (*str) {\n      char *attr;\n      char *value;\n\n      /* Get attribute */\n      attr = Cookies_parse_attr(&str);\n\n      /* Get the value for the attribute and store it */\n      if (first_attr) {\n         time_t now;\n         struct tm *tm;\n\n         if (*str != '=' || *attr == '\\0') {\n            /* disregard nameless cookie */\n            dFree(attr);\n            return NULL;\n         }\n         cookie = dNew0(CookieData_t, 1);\n         cookie->name = attr;\n         cookie->value = Cookies_parse_value(&str);\n\n         /* let's arbitrarily initialise with a year for now */\n         now = time(NULL);\n         tm = gmtime(&now);\n         ++tm->tm_year;\n         cookie->expires_at = mktime(tm);\n         if (cookie->expires_at == (time_t) -1)\n            cookie->expires_at = cookies_future_time;\n      } else if (dStrAsciiCasecmp(attr, \"Path\") == 0) {\n         value = Cookies_parse_value(&str);\n         dFree(cookie->path);\n         cookie->path = value;\n      } else if (dStrAsciiCasecmp(attr, \"Domain\") == 0) {\n         value = Cookies_parse_value(&str);\n         dFree(cookie->domain);\n         cookie->domain = value;\n      } else if (dStrAsciiCasecmp(attr, \"Max-Age\") == 0) {\n         value = Cookies_parse_value(&str);\n         if (isdigit(*value) || *value == '-') {\n            long age;\n            time_t now = time(NULL);\n            struct tm *tm = gmtime(&now);\n\n            errno = 0;\n            age = (*value == '-') ? 0 : strtol(value, NULL, 10);\n\n            if (errno == ERANGE ||\n                (age > 0 && (age > INT_MAX - tm->tm_sec))) {\n               /* let's not overflow */\n               tm->tm_sec = INT_MAX;\n            } else {\n               tm->tm_sec += age;\n            }\n            cookie->expires_at = mktime(tm);\n            if (age > 0 && cookie->expires_at == (time_t) -1) {\n               cookie->expires_at = cookies_future_time;\n            }\n            _MSG(\"Cookie to expire at %s\", ctime(&cookie->expires_at));\n            expires = max_age = TRUE;\n         }\n         dFree(value);\n      } else if (dStrAsciiCasecmp(attr, \"Expires\") == 0) {\n         if (!max_age) {\n            struct tm *tm;\n\n            value = Cookies_parse_value(&str);\n            Cookies_unquote_string(value);\n            _MSG(\"Expires attribute gives %s\\n\", value);\n            tm = Cookies_parse_date(value);\n            if (tm) {\n               tm->tm_sec += Cookies_server_timediff(server_date);\n               cookie->expires_at = mktime(tm);\n               if (cookie->expires_at == (time_t) -1 && tm->tm_year >= 138) {\n                  /* Just checking tm_year does not ensure that the problem was\n                   * inability to represent a distant date...\n                   */\n                  cookie->expires_at = cookies_future_time;\n               }\n               _MSG(\"Cookie to expire at %s\", ctime(&cookie->expires_at));\n               dFree(tm);\n            } else {\n               cookie->expires_at = (time_t) -1;\n            }\n            expires = TRUE;\n            dFree(value);\n         } else {\n            Cookies_eat_value(&str);\n         }\n      } else if (dStrAsciiCasecmp(attr, \"Secure\") == 0) {\n         cookie->secure = TRUE;\n         Cookies_eat_value(&str);\n      } else if (dStrAsciiCasecmp(attr, \"HttpOnly\") == 0) {\n         Cookies_eat_value(&str);\n      } else {\n         MSG(\"Cookie contains unknown attribute: '%s'\\n\", attr);\n         Cookies_eat_value(&str);\n      }\n\n      if (first_attr)\n         first_attr = FALSE;\n      else\n         dFree(attr);\n\n      if (*str == ';')\n         str++;\n   }\n   cookie->session_only = expires == FALSE;\n   return cookie;\n}\n\n/*\n * Compare cookies by host_only, name, and path. Return 0 if equal.\n */\nstatic int Cookies_cmp(const void *a, const void *b)\n{\n   const CookieData_t *ca = a, *cb = b;\n\n   return (ca->host_only != cb->host_only) ||\n          (strcmp(ca->name, cb->name) != 0) ||\n          (strcmp(ca->path, cb->path) != 0);\n}\n\n/*\n * Is the domain an IP address?\n */\nstatic bool_t Cookies_domain_is_ip(const char *domain)\n{\n   uint_t len;\n\n   if (!domain)\n      return FALSE;\n\n   len = strlen(domain);\n\n   if (len == strspn(domain, \"0123456789.\")) {\n      _MSG(\"an IPv4 address\\n\");\n      return TRUE;\n   }\n   if (strchr(domain, ':') &&\n       (len == strspn(domain, \"0123456789abcdefABCDEF:.\"))) {\n      /* The precise format is shown in section 3.2.2 of rfc 3986 */\n      MSG(\"an IPv6 address\\n\");\n      return TRUE;\n   }\n   return FALSE;\n}\n\n/*\n * Check whether url_path path-matches cookie_path\n *\n * Note different user agents apparently vary in path-matching behaviour,\n * but this is the recommended method at the moment.\n */\nstatic bool_t Cookies_path_matches(const char *url_path,\n                                   const char *cookie_path)\n{\n   bool_t ret = TRUE;\n\n   if (!url_path || !cookie_path) {\n      ret = FALSE;\n   } else {\n      uint_t c_len = strlen(cookie_path);\n      uint_t u_len = strlen(url_path);\n\n      ret = (!strncmp(cookie_path, url_path, c_len) &&\n             ((c_len == u_len) ||\n              (c_len > 0 && cookie_path[c_len - 1] == '/') ||\n              (url_path[c_len] == '/')));\n   }\n   return ret;\n}\n\n/*\n * If cookie path is not properly set, remedy that.\n */\nstatic void Cookies_validate_path(CookieData_t *cookie, const char *url_path)\n{\n   if (!cookie->path || cookie->path[0] != '/') {\n      dFree(cookie->path);\n\n      if (url_path) {\n         uint_t len = strlen(url_path);\n\n         while (len && url_path[len] != '/')\n            len--;\n         cookie->path = dStrndup(url_path, len ? len : 1);\n      } else {\n         cookie->path = dStrdup(\"/\");\n      }\n   }\n}\n\n/*\n * Check whether host name A domain-matches host name B.\n */\nstatic bool_t Cookies_domain_matches(char *A, char *B)\n{\n   int diff;\n\n   if (!A || !*A || !B || !*B)\n      return FALSE;\n\n   if (*B == '.')\n      B++;\n\n   /* Should we concern ourselves with trailing dots in matching (here or\n    * elsewhere)? The HTTP State people have found that most user agents\n    * don't, so: No.\n    */\n\n   if (!dStrAsciiCasecmp(A, B))\n      return TRUE;\n\n   if (Cookies_domain_is_ip(B))\n      return FALSE;\n\n   diff = strlen(A) - strlen(B);\n\n   if (diff > 0) {\n      /* B is the tail of A, and the match is preceded by a '.' */\n      return (dStrAsciiCasecmp(A + diff, B) == 0 && A[diff - 1] == '.');\n   } else {\n      return FALSE;\n   }\n}\n\n/*\n * Based on the host, how many internal dots do we need in a cookie domain\n * to make it valid? e.g., \"org\" is not on the list, so dillo.org is a safe\n * cookie domain, but \"uk\" is on the list, so ac.uk is not safe.\n *\n * This is imperfect, but it's something. Specifically, checking for these\n * TLDs is the solution that Konqueror used once upon a time, according to\n * reports.\n */\nstatic uint_t Cookies_internal_dots_required(const char *host)\n{\n   uint_t ret = 1;\n\n   if (host) {\n      int start, after, tld_len;\n\n      /* We may be able to trust the format of the host string more than\n       * I am here. Trailing dots and no dots are real possibilities, though.\n       */\n      after = strlen(host);\n      if (after > 0 && host[after - 1] == '.')\n         after--;\n      start = after;\n      while (start > 0 && host[start - 1] != '.')\n         start--;\n      tld_len = after - start;\n\n      if (tld_len > 0) {\n         /* These TLDs were chosen by examining the current publicsuffix list\n          * in October 2014 and picking out those where it was simplest for\n          * them to describe the situation by beginning with a \"*.[tld]\" rule\n          * or every rule was \"[something].[tld]\".\n          */\n         const char *const tlds[] = {\"bd\",\"bn\",\"ck\",\"cy\",\"er\",\"fj\",\"fk\",\n                                     \"gu\",\"il\",\"jm\",\"ke\",\"kh\",\"kw\",\"mm\",\"mz\",\n                                     \"ni\",\"np\",\"pg\",\"ye\",\"za\",\"zm\",\"zw\"};\n         uint_t i, tld_num = sizeof(tlds) / sizeof(tlds[0]);\n\n         for (i = 0; i < tld_num; i++) {\n            if (strlen(tlds[i]) == (uint_t) tld_len &&\n                !dStrnAsciiCasecmp(tlds[i], host + start, tld_len)) {\n               _MSG(\"TLD code matched %s\\n\", tlds[i]);\n               ret++;\n               break;\n            }\n         }\n      }\n   }\n   return ret;\n}\n\n/*\n * Validate cookies domain against some security checks.\n */\nstatic bool_t Cookies_validate_domain(CookieData_t *cookie, char *host)\n{\n   uint_t i, internal_dots;\n\n   if (!cookie->domain) {\n      cookie->domain = dStrdup(host);\n      cookie->host_only = TRUE;\n      return TRUE;\n   }\n\n   if (!Cookies_domain_matches(host, cookie->domain))\n      return FALSE;\n\n   internal_dots = 0;\n   for (i = 1; i < strlen(cookie->domain) - 1; i++) {\n      if (cookie->domain[i] == '.')\n         internal_dots++;\n   }\n\n   /* All of this dots business is a weak hack.\n    * TODO: accept the publicsuffix.org list as an optional external file.\n    */\n   if (internal_dots < Cookies_internal_dots_required(host)) {\n      MSG(\"not enough dots in %s\\n\", cookie->domain);\n      return FALSE;\n   }\n\n   _MSG(\"host %s and domain %s is all right\\n\", host, cookie->domain);\n   return TRUE;\n}\n\n/*\n * Set the value corresponding to the cookie string\n * Return value: 0 set OK, -1 disabled, -2 denied, -3 rejected.\n */\nstatic int Cookies_set(char *cookie_string, char *url_host,\n                       char *url_path, char *server_date)\n{\n   CookieControlAction action;\n   CookieData_t *cookie;\n   int ret = -1;\n\n   if (disabled)\n      return ret;\n\n   action = Cookies_control_check_domain(url_host);\n   if (action == COOKIE_DENY) {\n      MSG(\"denied SET for %s\\n\", url_host);\n      ret = -2;\n\n   } else {\n      MSG(\"%s SETTING: %s\\n\", url_host, cookie_string);\n      ret = -3;\n      if ((cookie = Cookies_parse(cookie_string, server_date))) {\n         if (Cookies_validate_domain(cookie, url_host)) {\n            Cookies_validate_path(cookie, url_path);\n            if (action == COOKIE_ACCEPT_SESSION)\n               cookie->session_only = TRUE;\n            Cookies_add_cookie(cookie);\n            ret = 0;\n         } else {\n            MSG(\"Rejecting cookie for domain %s from host %s path %s\\n\",\n                cookie->domain, url_host, url_path);\n            Cookies_free_cookie(cookie);\n         }\n      }\n   }\n\n   return ret;\n}\n\n/*\n * Compare the cookie with the supplied data to see whether it matches\n */\nstatic bool_t Cookies_match(CookieData_t *cookie, const char *url_path,\n                            bool_t host_only_val, bool_t is_tls)\n{\n   if (cookie->host_only != host_only_val)\n      return FALSE;\n\n   /* Insecure cookies match both secure and insecure urls, secure\n      cookies match only secure urls */\n   if (cookie->secure && !is_tls)\n      return FALSE;\n\n   if (!Cookies_path_matches(url_path, cookie->path))\n      return FALSE;\n\n   /* It's a match */\n   return TRUE;\n}\n\nstatic void Cookies_add_matching_cookies(const char *domain,\n                                         const char *url_path,\n                                         bool_t host_only_val,\n                                         Dlist *matching_cookies,\n                                         bool_t is_tls)\n{\n   DomainNode *node = dList_find_sorted(domains, domain,\n                                        Domain_node_by_domain_cmp);\n   if (node) {\n      int i;\n      CookieData_t *cookie;\n      Dlist *domain_cookies = node->cookies;\n\n      for (i = 0; (cookie = dList_nth_data(domain_cookies, i)); ++i) {\n         /* Remove expired cookie. */\n         if (difftime(cookie->expires_at, time(NULL)) < 0) {\n            _MSG(\"Goodbye, expired cookie %s=%s d:%s p:%s\\n\", cookie->name,\n                 cookie->value, cookie->domain, cookie->path);\n            dList_remove(domain_cookies, cookie);\n            dList_remove_fast(all_cookies, cookie);\n            Cookies_free_cookie(cookie);\n            --i; continue;\n         }\n         /* Check if the cookie matches the requesting URL */\n         if (Cookies_match(cookie, url_path, host_only_val, is_tls)) {\n            int j;\n            CookieData_t *curr;\n            uint_t path_length = strlen(cookie->path);\n\n            cookie->last_used = cookies_use_counter;\n\n            /* Longest cookies go first */\n            for (j = 0;\n                 (curr = dList_nth_data(matching_cookies, j)) &&\n                  strlen(curr->path) >= path_length;\n                 j++) ;\n            dList_insert_pos(matching_cookies, cookie, j);\n         }\n      }\n\n      if (dList_length(domain_cookies) == 0)\n         Cookies_delete_node(node);\n   }\n}\n\n/*\n * Return a string that contains all relevant cookies as headers.\n */\nstatic char *Cookies_get(char *url_host, char *url_path,\n                         char *url_scheme)\n{\n   char *domain_str, *str;\n   CookieData_t *cookie;\n   Dlist *matching_cookies;\n   bool_t is_tls, is_ip_addr, host_only_val;\n\n   Dstr *cookie_dstring;\n   int i;\n\n   if (disabled)\n      return dStrdup(\"\");\n\n   matching_cookies = dList_new(8);\n\n   /* Check if the protocol is secure or not */\n   is_tls = (!dStrAsciiCasecmp(url_scheme, \"https\"));\n\n   is_ip_addr = Cookies_domain_is_ip(url_host);\n\n   /* If a cookie is set that lacks a Domain attribute, its domain is set to\n    * the server's host and the host_only flag is set for that cookie. Such a\n    * cookie can only be sent back to that host. Cookies with Domain attrs do\n    * not have the host_only flag set, and may be sent to subdomains. Domain\n    * attrs can have leading dots, which should be ignored for matching\n    * purposes.\n    */\n   host_only_val = FALSE;\n   if (!is_ip_addr) {\n      /* e.g., sub.example.com set a cookie with domain \".sub.example.com\". */\n      domain_str = dStrconcat(\".\", url_host, NULL);\n      Cookies_add_matching_cookies(domain_str, url_path, host_only_val,\n                                   matching_cookies, is_tls);\n      dFree(domain_str);\n   }\n   host_only_val = TRUE;\n   /* e.g., sub.example.com set a cookie with no domain attribute. */\n   Cookies_add_matching_cookies(url_host, url_path, host_only_val,\n                                matching_cookies, is_tls);\n   host_only_val = FALSE;\n   /* e.g., sub.example.com set a cookie with domain \"sub.example.com\". */\n   Cookies_add_matching_cookies(url_host, url_path, host_only_val,\n                                matching_cookies, is_tls);\n\n   if (!is_ip_addr) {\n      for (domain_str = strchr(url_host+1, '.');\n           domain_str != NULL && *domain_str;\n           domain_str = strchr(domain_str+1, '.')) {\n         /* e.g., sub.example.com set a cookie with domain \".example.com\". */\n         Cookies_add_matching_cookies(domain_str, url_path, host_only_val,\n                                      matching_cookies, is_tls);\n         if (domain_str[1]) {\n            domain_str++;\n            /* e.g., sub.example.com set a cookie with domain \"example.com\".*/\n            Cookies_add_matching_cookies(domain_str, url_path, host_only_val,\n                                         matching_cookies, is_tls);\n         }\n      }\n   }\n\n   /* Found the cookies, now make the string */\n   cookie_dstring = dStr_new(\"\");\n   if (dList_length(matching_cookies) > 0) {\n\n      dStr_sprintfa(cookie_dstring, \"Cookie: \");\n\n      for (i = 0; (cookie = dList_nth_data(matching_cookies, i)); ++i) {\n         dStr_sprintfa(cookie_dstring, \"%s=%s\", cookie->name, cookie->value);\n         dStr_append(cookie_dstring,\n                     dList_length(matching_cookies) > i + 1 ? \"; \" : \"\\r\\n\");\n      }\n   }\n\n   dList_free(matching_cookies);\n   str = cookie_dstring->str;\n   dStr_free(cookie_dstring, FALSE);\n\n   if (*str) {\n      MSG(\"%s GETTING: %s\", url_host, str);\n      cookies_use_counter++;\n   }\n   return str;\n}\n\n/* -------------------------------------------------------------\n *                    Access control routines\n * ------------------------------------------------------------- */\n\n\n/*\n * Get the cookie control rules (from cookiesrc).\n * Return value:\n *   0 = Parsed OK, with cookies enabled\n *   1 = Parsed OK, with cookies disabled\n *   2 = Can't open the control file\n */\nstatic int Cookie_control_init(void)\n{\n   CookieControl cc;\n   FILE *stream;\n   char *filename, *rc;\n   char line[LINE_MAXLEN];\n   char domain[LINE_MAXLEN];\n   char rule[LINE_MAXLEN];\n   bool_t enabled = FALSE;\n\n   /* Get a file pointer */\n   filename = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/cookiesrc\", NULL);\n   stream = Cookies_fopen(filename, \"r\", \"DEFAULT DENY\\n\");\n   dFree(filename);\n\n   if (!stream)\n      return 2;\n\n   /* Get all lines in the file */\n   while (!feof(stream)) {\n      line[0] = '\\0';\n      rc = fgets(line, LINE_MAXLEN, stream);\n      if (!rc && ferror(stream)) {\n         MSG(\"Error while reading rule from cookiesrc: %s\\n\",\n             dStrerror(errno));\n         break; /* bail out */\n      }\n\n      /* Remove leading and trailing whitespaces */\n      dStrstrip(line);\n\n      if (line[0] != '\\0' && line[0] != '#') {\n         int i = 0, j = 0;\n\n         /* Get the domain */\n         while (line[i] != '\\0' && !dIsspace(line[i]))\n            domain[j++] = line[i++];\n         domain[j] = '\\0';\n\n         /* Skip past whitespaces */\n         while (dIsspace(line[i]))\n            i++;\n\n         /* Get the rule */\n         j = 0;\n         while (line[i] != '\\0' && !dIsspace(line[i]))\n            rule[j++] = line[i++];\n         rule[j] = '\\0';\n\n         if (dStrAsciiCasecmp(rule, \"ACCEPT\") == 0)\n            cc.action = COOKIE_ACCEPT;\n         else if (dStrAsciiCasecmp(rule, \"ACCEPT_SESSION\") == 0)\n            cc.action = COOKIE_ACCEPT_SESSION;\n         else if (dStrAsciiCasecmp(rule, \"DENY\") == 0)\n            cc.action = COOKIE_DENY;\n         else {\n            MSG(\"Cookies: rule '%s' for domain '%s' is not recognised.\\n\",\n                rule, domain);\n            continue;\n         }\n\n         cc.domain = dStrdup(domain);\n         if (dStrAsciiCasecmp(cc.domain, \"DEFAULT\") == 0) {\n            /* Set the default action */\n            default_action = cc.action;\n            dFree(cc.domain);\n         } else {\n            int i;\n            uint_t len = strlen(cc.domain);\n\n            /* Insert into list such that longest rules come first. */\n            a_List_add(ccontrol, num_ccontrol, num_ccontrol_max);\n            for (i = num_ccontrol++;\n                 i > 0 && (len > strlen(ccontrol[i-1].domain));\n                 i--) {\n               ccontrol[i] = ccontrol[i-1];\n            }\n            ccontrol[i] = cc;\n         }\n\n         if (cc.action != COOKIE_DENY)\n            enabled = TRUE;\n      }\n   }\n\n   fclose(stream);\n\n   return (enabled ? 0 : 1);\n}\n\n/*\n * Check the rules for an appropriate action for this domain.\n * The rules are ordered by domain length, with longest first, so the\n * first match is the most specific.\n */\nstatic CookieControlAction Cookies_control_check_domain(const char *domain)\n{\n   int i, diff;\n\n   for (i = 0; i < num_ccontrol; i++) {\n      if (ccontrol[i].domain[0] == '.') {\n         diff = strlen(domain) - strlen(ccontrol[i].domain);\n         if (diff >= 0) {\n            if (dStrAsciiCasecmp(domain + diff, ccontrol[i].domain) != 0)\n               continue;\n         } else {\n            continue;\n         }\n      } else {\n         if (dStrAsciiCasecmp(domain, ccontrol[i].domain) != 0)\n            continue;\n      }\n\n      /* If we got here we have a match */\n      return( ccontrol[i].action );\n   }\n\n   return default_action;\n}\n\n/* -- Dpi parser ----------------------------------------------------------- */\n\n/*\n * Parse a data stream (dpi protocol)\n * Note: Buf is a zero terminated string\n * Return code: { 0:OK, 1:Abort, 2:Close }\n */\nstatic int srv_parse_tok(Dsh *sh, ClientInfo *client, char *Buf)\n{\n   char *cmd, *cookie, *host, *path;\n   int ret = 1;\n   size_t BufSize = strlen(Buf);\n\n   cmd = a_Dpip_get_attr_l(Buf, BufSize, \"cmd\");\n\n   if (!cmd) {\n      /* abort */\n   } else if (client->status == 0) {\n      /* authenticate */\n      if (a_Dpip_check_auth(Buf) == 1) {\n         client->status = 1;\n         ret = 0;\n      }\n   } else if (strcmp(cmd, \"DpiBye\") == 0) {\n      dFree(cmd);\n      MSG(\"(pid %d): Got DpiBye.\\n\", (int)getpid());\n      exit(0);\n\n   } else if (strcmp(cmd, \"set_cookie\") == 0) {\n      int st;\n      char *date;\n\n      cookie = a_Dpip_get_attr_l(Buf, BufSize, \"cookie\");\n      host = a_Dpip_get_attr_l(Buf, BufSize, \"host\");\n      path = a_Dpip_get_attr_l(Buf, BufSize, \"path\");\n      date = a_Dpip_get_attr_l(Buf, BufSize, \"date\");\n\n      st = Cookies_set(cookie, host, path, date);\n\n      dFree(cmd);\n      cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"set_cookie_answer\",\n                             st == 0 ? \"ok\" : \"not set\");\n      a_Dpip_dsh_write_str(sh, 1, cmd);\n\n      dFree(date);\n      dFree(path);\n      dFree(host);\n      dFree(cookie);\n      ret = 2;\n\n   } else if (strcmp(cmd, \"get_cookie\") == 0) {\n      char *scheme = a_Dpip_get_attr_l(Buf, BufSize, \"scheme\");\n\n      host = a_Dpip_get_attr_l(Buf, BufSize, \"host\");\n      path = a_Dpip_get_attr_l(Buf, BufSize, \"path\");\n\n      cookie = Cookies_get(host, path, scheme);\n      dFree(scheme);\n      dFree(path);\n      dFree(host);\n\n      dFree(cmd);\n      cmd = a_Dpip_build_cmd(\"cmd=%s cookie=%s\", \"get_cookie_answer\", cookie);\n\n      if (a_Dpip_dsh_write_str(sh, 1, cmd)) {\n          ret = 1;\n      } else {\n          _MSG(\"a_Dpip_dsh_write_str: SUCCESS cmd={%s}\\n\", cmd);\n          ret = 2;\n      }\n      dFree(cookie);\n   }\n   dFree(cmd);\n\n   return ret;\n}\n\n/* --  Termination handlers ----------------------------------------------- */\n/*\n * (was to delete the local namespace socket),\n *  but this is handled by 'dpid' now.\n */\nstatic void cleanup(void)\n{\n  Cookies_save_and_free();\n  MSG(\"cleanup\\n\");\n  /* no more cleanup required */\n}\n\n/*\n * Perform any necessary cleanups upon abnormal termination\n */\nstatic void termination_handler(int signum)\n{\n  exit(signum);\n}\n\n\n/*\n * -- MAIN -------------------------------------------------------------------\n */\nint main(void) {\n   struct sockaddr_in sin;\n   socklen_t address_size;\n   ClientInfo *client;\n   int sock_fd, code;\n   char *buf;\n   Dsh *sh;\n\n   /* Arrange the cleanup function for terminations via exit() */\n   atexit(cleanup);\n\n   /* Arrange the cleanup function for abnormal terminations */\n   if (signal (SIGINT, termination_handler) == SIG_IGN)\n     signal (SIGINT, SIG_IGN);\n   if (signal (SIGHUP, termination_handler) == SIG_IGN)\n     signal (SIGHUP, SIG_IGN);\n   if (signal (SIGTERM, termination_handler) == SIG_IGN)\n     signal (SIGTERM, SIG_IGN);\n\n   Cookies_init();\n   MSG(\"(v.1) accepting connections...\\n\");\n\n   if (disabled)\n      exit(1);\n\n   /* some OSes may need this... */\n   address_size = sizeof(struct sockaddr_in);\n\n   while (1) {\n      sock_fd = accept(STDIN_FILENO, (struct sockaddr *)&sin, &address_size);\n      if (sock_fd == -1) {\n         perror(\"[accept]\");\n         exit(1);\n      }\n\n      /* create the Dsh structure */\n      sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);\n      client = dNew(ClientInfo,1);\n      client->sh = sh;\n      client->status = 0;\n\n      while (1) {\n         code = 1;\n         if ((buf = a_Dpip_dsh_read_token(sh, 1)) != NULL) {\n            /* Let's see what we fished... */\n            _MSG(\" buf = {%s}\\n\", buf);\n            code = srv_parse_tok(sh, client, buf);\n            dFree(buf);\n         }\n\n         _MSG(\" code = %d %s\\n\", code, code == 1 ? \"EXIT\" : \"BREAK\");\n         if (code == 1) {\n            exit(1);\n         } else if (code == 2) {\n            break;\n         }\n      }\n\n      _MSG(\"Closing Dsh\\n\");\n      a_Dpip_dsh_close(sh);\n      a_Dpip_dsh_free(sh);\n      dFree(client);\n\n   }/*while*/\n\n   return 0;\n}\n\n#endif /* !DISABLE_COOKIES */\n"
  },
  {
    "path": "dpi/datauri.c",
    "content": "/*\n * File: datauri.c\n *\n * Copyright (C) 2006-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * Filter dpi for the \"data:\" URI scheme (RFC 2397).\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <unistd.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n#include <errno.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n\n/*\n * Debugging macros\n */\n#define SILENT 1\n#define _MSG(...)\n#if SILENT\n #define MSG(...)\n#else\n #define MSG(...)  fprintf(stderr, \"[datauri dpi]: \" __VA_ARGS__)\n#endif\n\n/*\n * Global variables\n */\nstatic Dsh *sh = NULL;\n\nstatic void b64strip_illegal_chars(unsigned char* str)\n{\n   unsigned char *p, *s = str;\n\n   MSG(\"len=%d{%s}\\n\", strlen((char*)str), str);\n\n   for (p = s; (*p = *s); ++s) {\n      if (isascii(*p) && (isalnum(*p) || strchr(\"+/=\", *p)))\n         ++p;\n   }\n\n   MSG(\"len=%d{%s}\\n\", strlen((char *)str), str);\n}\n\nstatic int b64decode(unsigned char* str)\n{\n   unsigned char *cur, *start;\n   int d, dlast, phase;\n   unsigned char c;\n   static int table[256] = {\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 00-0F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 10-1F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,  /* 20-2F */\n      52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,  /* 30-3F */\n      -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,  /* 40-4F */\n      15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,  /* 50-5F */\n      -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,  /* 60-6F */\n      41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,  /* 70-7F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 80-8F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 90-9F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* A0-AF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* B0-BF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* C0-CF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* D0-DF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* E0-EF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1   /* F0-FF */\n   };\n\n   d = dlast = phase = 0;\n   start = str;\n   for (cur = str; *cur != '\\0'; ++cur ) {\n      // jer: treat line endings as physical breaks.\n      //if (*cur == '\\n' || *cur == '\\r'){phase = dlast = 0; continue;}\n      d = table[(int)*cur];\n      if (d != -1) {\n         switch(phase) {\n         case 0:\n            ++phase;\n            break;\n         case 1:\n            c = ((dlast << 2) | ((d & 0x30) >> 4));\n            *str++ = c;\n            ++phase;\n            break;\n         case 2:\n            c = (((dlast & 0xf) << 4) | ((d & 0x3c) >> 2));\n            *str++ = c;\n            ++phase;\n            break;\n         case 3:\n            c = (((dlast & 0x03 ) << 6) | d);\n            *str++ = c;\n            phase = 0;\n            break;\n         }\n         dlast = d;\n      }\n   }\n   *str = '\\0';\n   return str - start;\n}\n\n/* Modified from src/url.c --------------------------------------------------*/\n\n/*\n * Given an hex octet (e.g., e3, 2F, 20), return the corresponding\n * character if the octet is valid, and -1 otherwise\n */\nstatic int Url_decode_hex_octet(const char *s)\n{\n   int hex_value;\n   char *tail, hex[3];\n\n   if (s && (hex[0] = s[0]) && (hex[1] = s[1])) {\n      hex[2] = 0;\n      hex_value = strtol(hex, &tail, 16);\n      if (tail - hex == 2)\n         return hex_value;\n   }\n   return -1;\n}\n\n/*\n * Parse possible hexadecimal octets in the URI path.\n * Returns a new allocated string.\n */\nchar *a_Url_decode_hex_str(const char *str, size_t *p_sz)\n{\n   char *new_str, *dest;\n   int i, val;\n\n   if (!str) {\n      *p_sz = 0;\n      return NULL;\n   }\n\n   dest = new_str = dNew(char, strlen(str) + 1);\n   for (i = 0; str[i]; i++) {\n      *dest++ = (str[i] == '%' && (val = Url_decode_hex_octet(str+i+1)) >= 0) ?\n                i+=2, val : str[i];\n   }\n   *dest = 0;\n\n   *p_sz = (size_t)(dest - new_str);\n   new_str = dRealloc(new_str, sizeof(char) * (dest - new_str + 1));\n   return new_str;\n}\n\n/* end ----------------------------------------------------------------------*/\n\n/*\n * Send decoded data to dillo in an HTTP envelope.\n */\nstatic void send_decoded_data(const char *url, const char *mime_type,\n                              unsigned char *data, size_t data_sz)\n{\n   char *d_cmd;\n\n   /* Send dpip tag */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n   a_Dpip_dsh_write_str(sh, 1, d_cmd);\n   dFree(d_cmd);\n\n   /* Send HTTP header. */\n   a_Dpip_dsh_write_str(sh, 0, \"Content-type: \");\n   a_Dpip_dsh_write_str(sh, 0, mime_type);\n   a_Dpip_dsh_write_str(sh, 1, \"\\n\\n\");\n\n   /* Send message */\n   a_Dpip_dsh_write(sh, 0, (char *)data, data_sz);\n}\n\nstatic void send_failure_message(const char *url, const char *mime_type,\n                                 unsigned char *data, size_t data_sz)\n{\n   char *d_cmd;\n   char buf[1024];\n\n   const char *msg =\n\"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\\n\"\n\"<html><body>\\n\"\n\"<hr><h1>Datauri dpi</h1><hr>\\n\"\n\"<p><b>Can't parse datauri:</b><br>\\n\";\n   const char *msg_mime_type=\"text/html\";\n\n   /* Send dpip tag */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n   a_Dpip_dsh_write_str(sh, 1, d_cmd);\n   dFree(d_cmd);\n\n   /* Send HTTP header. */\n   a_Dpip_dsh_write_str(sh, 0, \"Content-type: \");\n   a_Dpip_dsh_write_str(sh, 0, msg_mime_type);\n   a_Dpip_dsh_write_str(sh, 1, \"\\n\\n\");\n\n   /* Send message */\n   a_Dpip_dsh_write_str(sh, 0, msg);\n\n   /* send some debug info */\n   snprintf(buf, 1024, \"mime_type: %s<br>data size: %d<br>data: %s<br>\",\n            mime_type, (int)data_sz, data);\n   a_Dpip_dsh_write_str(sh, 0, buf);\n\n   /* close page */\n   a_Dpip_dsh_write_str(sh, 0, \"</body></html>\");\n}\n\n/*\n * Get mime type from the data URI.\n */\nstatic char *datauri_get_mime(char *url)\n{\n   char buf[256];\n   char *mime_type = NULL, *p;\n   size_t len = 0;\n\n   if (dStrnAsciiCasecmp(url, \"data:\", 5) == 0) {\n      if ((p = strchr(url, ',')) && p - url < 256) {\n         url += 5;\n         len = p - url;\n         strncpy(buf, url, len);\n         buf[len] = 0;\n         /* strip \";base64\" */\n         if (len >= 7 && dStrAsciiCasecmp(buf + len - 7, \";base64\") == 0) {\n            len -= 7;\n            buf[len] = 0;\n         }\n      }\n\n      /* that's it, now handle omitted types */\n      if (len == 0) {\n         mime_type = dStrdup(\"text/plain;charset=US-ASCII\");\n      } else if (!dStrnAsciiCasecmp(buf, \"charset\", 7)) {\n         mime_type = dStrconcat(\"text/plain;\", buf, NULL);\n      } else {\n         mime_type = dStrdup(buf);\n      }\n   }\n\n   return mime_type;\n}\n\n/*\n * Return a decoded data string.\n */\nstatic unsigned char *datauri_get_data(char *url, size_t *p_sz)\n{\n   char *p;\n   int is_base64 = 0;\n   unsigned char *data = NULL;\n\n   if ((p = strchr(url, ',')) && p - url >= 12 &&  /* \"data:;base64\" */\n       dStrnAsciiCasecmp(p - 7, \";base64\", 7) == 0) {\n      is_base64 = 1;\n   }\n\n   if (p) {\n      ++p;\n      if (is_base64) {\n         data = (unsigned char *)Unescape_uri_str(p);\n         b64strip_illegal_chars(data);\n         *p_sz = (size_t) b64decode(data);\n      } else {\n         data = (unsigned char *)a_Url_decode_hex_str(p, p_sz);\n      }\n   } else {\n      data = (unsigned char *)dStrdup(\"\");\n      *p_sz = 0;\n   }\n\n   return data;\n}\n\n/*\n *\n */\nint main(void)\n{\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *mime_type;\n   unsigned char *data;\n   int rc;\n   size_t data_size = 0;\n\n   /* Initialize the SockHandler */\n   sh = a_Dpip_dsh_new(STDIN_FILENO, STDOUT_FILENO, 8*1024);\n\n   rc = chdir(\"/tmp\");\n   if (rc == -1) {\n      MSG(\"paths: error changing directory to /tmp: %s\\n\",\n          dStrerror(errno));\n   }\n\n   /* Authenticate our client... */\n   if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||\n       a_Dpip_check_auth(dpip_tag) < 0) {\n      MSG(\"can't authenticate request: %s\\n\", dStrerror(errno));\n      a_Dpip_dsh_close(sh);\n      return 1;\n   }\n   dFree(dpip_tag);\n\n   /* Read the dpi command from STDIN */\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   MSG(\"[%s]\\n\", dpip_tag);\n\n   cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n   url = a_Dpip_get_attr(dpip_tag, \"url\");\n   if (!cmd || !url) {\n      MSG(\"Error, cmd=%s, url=%s\\n\", cmd, url);\n      exit (EXIT_FAILURE);\n   }\n\n   /* Parse the data URI */\n   mime_type = datauri_get_mime(url);\n   data = datauri_get_data(url, &data_size);\n\n   MSG(\"mime_type: %s\\n\", mime_type);\n   MSG(\"data_size: %ld\\n\", (int)data_size);\n   MSG(\"data: {%s}\\n\", data);\n\n   if (mime_type && data) {\n      /* good URI */\n      send_decoded_data(url, mime_type, data, data_size);\n   } else {\n      /* malformed URI */\n      send_failure_message(url, mime_type, data, data_size);\n   }\n\n   dFree(data);\n   dFree(mime_type);\n   dFree(url);\n   dFree(cmd);\n   dFree(dpip_tag);\n\n   /* Finish the SockHandler */\n   a_Dpip_dsh_close(sh);\n   a_Dpip_dsh_free(sh);\n\n   return 0;\n}\n\n"
  },
  {
    "path": "dpi/datauri.c.orig",
    "content": "/*\n * File: datauri.c\n *\n * Copyright (C) 2006-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * Filter dpi for the \"data:\" URI scheme (RFC 2397).\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <unistd.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n#include <errno.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n\n/*\n * Debugging macros\n */\n#define SILENT 1\n#define _MSG(...)\n#if SILENT\n #define MSG(...)\n#else\n #define MSG(...)  fprintf(stderr, \"[datauri dpi]: \" __VA_ARGS__)\n#endif\n\n/*\n * Global variables\n */\nstatic Dsh *sh = NULL;\n\nstatic void b64strip_illegal_chars(unsigned char* str)\n{\n   unsigned char *p, *s = str;\n\n   MSG(\"len=%d{%s}\\n\", strlen((char*)str), str);\n\n   for (p = s; (*p = *s); ++s) {\n      if (isascii(*p) && (isalnum(*p) || strchr(\"+/=\", *p)))\n         ++p;\n   }\n\n   MSG(\"len=%d{%s}\\n\", strlen((char *)str), str);\n}\n\nstatic int b64decode(unsigned char* str)\n{\n   unsigned char *cur, *start;\n   int d, dlast, phase;\n   unsigned char c;\n   static int table[256] = {\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 00-0F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 10-1F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,  /* 20-2F */\n      52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,  /* 30-3F */\n      -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,  /* 40-4F */\n      15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,  /* 50-5F */\n      -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,  /* 60-6F */\n      41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,  /* 70-7F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 80-8F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* 90-9F */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* A0-AF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* B0-BF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* C0-CF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* D0-DF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,  /* E0-EF */\n      -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1   /* F0-FF */\n   };\n\n   d = dlast = phase = 0;\n   start = str;\n   for (cur = str; *cur != '\\0'; ++cur ) {\n      // jer: treat line endings as physical breaks.\n      //if (*cur == '\\n' || *cur == '\\r'){phase = dlast = 0; continue;}\n      d = table[(int)*cur];\n      if (d != -1) {\n         switch(phase) {\n         case 0:\n            ++phase;\n            break;\n         case 1:\n            c = ((dlast << 2) | ((d & 0x30) >> 4));\n            *str++ = c;\n            ++phase;\n            break;\n         case 2:\n            c = (((dlast & 0xf) << 4) | ((d & 0x3c) >> 2));\n            *str++ = c;\n            ++phase;\n            break;\n         case 3:\n            c = (((dlast & 0x03 ) << 6) | d);\n            *str++ = c;\n            phase = 0;\n            break;\n         }\n         dlast = d;\n      }\n   }\n   *str = '\\0';\n   return str - start;\n}\n\n/* Modified from src/url.c --------------------------------------------------*/\n\n/*\n * Given an hex octet (e.g., e3, 2F, 20), return the corresponding\n * character if the octet is valid, and -1 otherwise\n */\nstatic int Url_decode_hex_octet(const char *s)\n{\n   int hex_value;\n   char *tail, hex[3];\n\n   if (s && (hex[0] = s[0]) && (hex[1] = s[1])) {\n      hex[2] = 0;\n      hex_value = strtol(hex, &tail, 16);\n      if (tail - hex == 2)\n         return hex_value;\n   }\n   return -1;\n}\n\n/*\n * Parse possible hexadecimal octets in the URI path.\n * Returns a new allocated string.\n */\nchar *a_Url_decode_hex_str(const char *str, size_t *p_sz)\n{\n   char *new_str, *dest;\n   int i, val;\n\n   if (!str) {\n      *p_sz = 0;\n      return NULL;\n   }\n\n   dest = new_str = dNew(char, strlen(str) + 1);\n   for (i = 0; str[i]; i++) {\n      *dest++ = (str[i] == '%' && (val = Url_decode_hex_octet(str+i+1)) >= 0) ?\n                i+=2, val : str[i];\n   }\n   *dest = 0;\n\n   *p_sz = (size_t)(dest - new_str);\n   new_str = dRealloc(new_str, sizeof(char) * (dest - new_str + 1));\n   return new_str;\n}\n\n/* end ----------------------------------------------------------------------*/\n\n/*\n * Send decoded data to dillo in an HTTP envelope.\n */\nstatic void send_decoded_data(const char *url, const char *mime_type,\n                              unsigned char *data, size_t data_sz)\n{\n   char *d_cmd;\n\n   /* Send dpip tag */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n   a_Dpip_dsh_write_str(sh, 1, d_cmd);\n   dFree(d_cmd);\n\n   /* Send HTTP header. */\n   a_Dpip_dsh_write_str(sh, 0, \"Content-type: \");\n   a_Dpip_dsh_write_str(sh, 0, mime_type);\n   a_Dpip_dsh_write_str(sh, 1, \"\\n\\n\");\n\n   /* Send message */\n   a_Dpip_dsh_write(sh, 0, (char *)data, data_sz);\n}\n\nstatic void send_failure_message(const char *url, const char *mime_type,\n                                 unsigned char *data, size_t data_sz)\n{\n   char *d_cmd;\n   char buf[1024];\n\n   const char *msg =\n\"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\\n\"\n\"<html><body>\\n\"\n\"<hr><h1>Datauri dpi</h1><hr>\\n\"\n\"<p><b>Can't parse datauri:</b><br>\\n\";\n   const char *msg_mime_type=\"text/html\";\n\n   /* Send dpip tag */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n   a_Dpip_dsh_write_str(sh, 1, d_cmd);\n   dFree(d_cmd);\n\n   /* Send HTTP header. */\n   a_Dpip_dsh_write_str(sh, 0, \"Content-type: \");\n   a_Dpip_dsh_write_str(sh, 0, msg_mime_type);\n   a_Dpip_dsh_write_str(sh, 1, \"\\n\\n\");\n\n   /* Send message */\n   a_Dpip_dsh_write_str(sh, 0, msg);\n\n   /* send some debug info */\n   snprintf(buf, 1024, \"mime_type: %s<br>data size: %d<br>data: %s<br>\",\n            mime_type, (int)data_sz, data);\n   a_Dpip_dsh_write_str(sh, 0, buf);\n\n   /* close page */\n   a_Dpip_dsh_write_str(sh, 0, \"</body></html>\");\n}\n\n/*\n * Get mime type from the data URI.\n */\nstatic char *datauri_get_mime(char *url)\n{\n   char buf[256];\n   char *mime_type = NULL, *p;\n   size_t len = 0;\n\n   if (dStrnAsciiCasecmp(url, \"data:\", 5) == 0) {\n      if ((p = strchr(url, ',')) && p - url < 256) {\n         url += 5;\n         len = p - url;\n         strncpy(buf, url, len);\n         buf[len] = 0;\n         /* strip \";base64\" */\n         if (len >= 7 && dStrAsciiCasecmp(buf + len - 7, \";base64\") == 0) {\n            len -= 7;\n            buf[len] = 0;\n         }\n      }\n\n      /* that's it, now handle omitted types */\n      if (len == 0) {\n         mime_type = dStrdup(\"text/plain;charset=US-ASCII\");\n      } else if (!dStrnAsciiCasecmp(buf, \"charset\", 7)) {\n         mime_type = dStrconcat(\"text/plain;\", buf, NULL);\n      } else {\n         mime_type = dStrdup(buf);\n      }\n   }\n\n   return mime_type;\n}\n\n/*\n * Return a decoded data string.\n */\nstatic unsigned char *datauri_get_data(char *url, size_t *p_sz)\n{\n   char *p;\n   int is_base64 = 0;\n   unsigned char *data = NULL;\n\n   if ((p = strchr(url, ',')) && p - url >= 12 &&  /* \"data:;base64\" */\n       dStrnAsciiCasecmp(p - 7, \";base64\", 7) == 0) {\n      is_base64 = 1;\n   }\n\n   if (p) {\n      ++p;\n      if (is_base64) {\n         data = (unsigned char *)Unescape_uri_str(p);\n         b64strip_illegal_chars(data);\n         *p_sz = (size_t) b64decode(data);\n      } else {\n         data = (unsigned char *)a_Url_decode_hex_str(p, p_sz);\n      }\n   } else {\n      data = (unsigned char *)dStrdup(\"\");\n      *p_sz = 0;\n   }\n\n   return data;\n}\n\n/*\n *\n */\nint main(void)\n{\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *mime_type;\n   unsigned char *data;\n   int rc;\n   size_t data_size = 0;\n\n   /* Initialize the SockHandler */\n   sh = a_Dpip_dsh_new(STDIN_FILENO, STDOUT_FILENO, 8*1024);\n\n   rc = chdir(\"/tmp\");\n   if (rc == -1) {\n      MSG(\"paths: error changing directory to /tmp: %s\\n\",\n          dStrerror(errno));\n   }\n\n   /* Authenticate our client... */\n   if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||\n       a_Dpip_check_auth(dpip_tag) < 0) {\n      MSG(\"can't authenticate request: %s\\n\", dStrerror(errno));\n      a_Dpip_dsh_close(sh);\n      return 1;\n   }\n   dFree(dpip_tag);\n\n   /* Read the dpi command from STDIN */\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   MSG(\"[%s]\\n\", dpip_tag);\n\n   cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n   url = a_Dpip_get_attr(dpip_tag, \"url\");\n   if (!cmd || !url) {\n      MSG(\"Error, cmd=%s, url=%s\\n\", cmd, url);\n      exit (EXIT_FAILURE);\n   }\n\n   /* Parse the data URI */\n   mime_type = datauri_get_mime(url);\n   data = datauri_get_data(url, &data_size);\n\n   MSG(\"mime_type: %s\\n\", mime_type);\n   MSG(\"data_size: %d\\n\", (int)data_size);\n   MSG(\"data: {%s}\\n\", data);\n\n   if (mime_type && data) {\n      /* good URI */\n      send_decoded_data(url, mime_type, data, data_size);\n   } else {\n      /* malformed URI */\n      send_failure_message(url, mime_type, data, data_size);\n   }\n\n   dFree(data);\n   dFree(mime_type);\n   dFree(url);\n   dFree(cmd);\n   dFree(dpip_tag);\n\n   /* Finish the SockHandler */\n   a_Dpip_dsh_close(sh);\n   a_Dpip_dsh_free(sh);\n\n   return 0;\n}\n\n"
  },
  {
    "path": "dpi/downloads.cc",
    "content": "/*\n * File: downloads.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * A FLTK-based GUI for the downloads dpi (dillo plugin).\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <ctype.h>\n#include <math.h>\n#include <time.h>\n#include <signal.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/un.h>\n#include <sys/wait.h>\n\n#include <FL/Fl.H>\n#include <FL/fl_ask.H>\n#include <FL/fl_draw.H>\n#include <FL/Fl_File_Chooser.H>\n#include <FL/Fl_Window.H>\n#include <FL/Fl_Widget.H>\n#include <FL/Fl_Group.H>\n#include <FL/Fl_Scroll.H>\n#include <FL/Fl_Pack.H>\n#include <FL/Fl_Box.H>\n#include <FL/Fl_Button.H>\n\n#include \"dpiutil.h\"\n#include \"../dpip/dpip.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[downloads dpi]: \" __VA_ARGS__)\n\n/*\n * Class declarations\n */\n\n// ProgressBar widget --------------------------------------------------------\n\nclass ProgressBar : public Fl_Box {\nprotected:\n   double mMin;\n   double mMax;\n   double mPresent;\n   double mStep;\n   bool mShowPct, mShowMsg;\n   char mMsg[64];\n   Fl_Color mTextColor;\n   void draw();\npublic:\n   ProgressBar(int x, int y, int w, int h, const char *lbl = 0);\n   void range(double min, double max, double step = 1) {\n      mMin = min; mMax = max; mStep = step;\n   };\n   void step(double step)        { mPresent += step; redraw(); };\n   void move(double step);\n   double minimum()        { return mMin; }\n   double maximum()        { return mMax; }\n   void minimum(double nm) { mMin = nm; };\n   void maximum(double nm) { mMax = nm; };\n   double position  ()     { return mPresent; }\n   double step()           { return mStep; }\n   void position(double pos)     { mPresent = pos; redraw(); }\n   void showtext(bool st)        { mShowPct = st; }\n   void message(char *msg) { mShowMsg = true; strncpy(mMsg,msg,63); redraw(); }\n   bool showtext()               { return mShowPct; }\n   void text_color(Fl_Color col) { mTextColor = col; }\n   Fl_Color text_color()   { return mTextColor; }\n};\n\n// Download-item class -------------------------------------------------------\n\nclass DLItem {\n   enum {\n      ST_newline, ST_number, ST_discard, ST_copy\n   };\n\n   pid_t mPid;\n   int LogPipe[2];\n   char *shortname, *fullname, *useragent;\n   char *target_dir;\n   size_t log_len, log_max;\n   int log_state;\n   char *log_text;\n   time_t init_time;\n   char **dl_argv;\n   time_t twosec_time, onesec_time;\n   int twosec_bytesize, onesec_bytesize;\n   int init_bytesize, curr_bytesize, total_bytesize;\n   int DataDone, LogDone, ForkDone, UpdatesDone, WidgetDone;\n   int DownloaderStatus;\n\n   int gw, gh;\n   Fl_Group *group;\n   ProgressBar *prBar;\n   Fl_Button *prButton;\n   Fl_Widget *prTitle, *prGot, *prSize, *prRate, *pr_Rate, *prETA, *prETAt;\n\npublic:\n   DLItem(const char *full_filename, const char *url, const char *user_agent);\n   ~DLItem();\n   void child_init();\n   void father_init();\n   void update_size(int new_sz);\n   void log_text_add(const char *buf, ssize_t st);\n   void log_text_show();\n   void abort_dl();\n   void prButton_cb();\n   pid_t pid() { return mPid; }\n   void pid(pid_t p) { mPid = p; }\n   void child_finished(int status);\n   void status_msg(const char *msg) { prBar->message((char*)msg); }\n   Fl_Widget *get_widget() { return group; }\n   int widget_done() { return WidgetDone; }\n   void widget_done(int val) { WidgetDone = val; }\n   int updates_done() { return UpdatesDone; }\n   void updates_done(int val) { UpdatesDone = val; }\n   int fork_done() { return ForkDone; }\n   void fork_done(int val) { ForkDone = val; }\n   int log_done() { return LogDone; }\n   void log_done(int val) { LogDone = val; }\n   int downloader_status() { return DownloaderStatus; }\n   void downloader_status(int val) { DownloaderStatus = val; }\n   void update_prSize(int newsize);\n   void update();\n};\n\n// DLItem List ---------------------------------------------------------------\n\n/// BUG: make dynamic\nclass DLItemList {\n   DLItem *mList[32];\n   int mNum, mMax;\n\npublic:\n   DLItemList() { mNum = 0; mMax = 32; }\n   ~DLItemList() { }\n   int num() { return mNum; }\n   void add(DLItem *i) { if (mNum < mMax) mList[mNum++] = i; }\n   DLItem *get(int n) { return (n >= 0 && n < mNum) ? mList[n] : NULL; }\n   void del(int n) { if (n >= 0 && n < mNum) mList[n] = mList[--mNum]; }\n};\n\n// DLWin ---------------------------------------------------------------------\n\nclass DLWin {\n   DLItemList *mDList;\n   Fl_Window *mWin;\n   Fl_Scroll *mScroll;\n   Fl_Pack *mPG;\n\npublic:\n   DLWin(int ww, int wh);\n   void add(const char *full_filename, const char *url, const char *user_agent);\n   void del(int n_item);\n   int num();\n   int num_running();\n   void listen(int req_fd);\n   void show() { mWin->show(); }\n   void hide() { mWin->hide(); }\n   void abort_all();\n};\n\n/*\n * FLTK cannot be dissuaded from interpreting '@' in a tooltip\n * as indicating a symbol unless we escape it.\n */\nstatic char *escape_tooltip(const char *buf, ssize_t len)\n{\n   if (len < 0)\n      len = 0;\n\n   char *ret = (char *) malloc(2 * len + 1);\n   char *dest = ret;\n\n   while (len-- > 0) {\n      if (*buf == '@')\n         *dest++ = *buf;\n      *dest++ = *buf++;\n   }\n   *dest = '\\0';\n\n   return ret;\n}\n\n\n/*\n * Global variables\n */\n\n// SIGCHLD mask\nsigset_t mask_sigchld;\n\n// SIGCHLD flag\nvolatile sig_atomic_t caught_sigchld = 0;\n\n// The download window object\nstatic class DLWin *dl_win = NULL;\n\n\n\n// ProgressBar widget --------------------------------------------------------\n\nvoid ProgressBar::move(double step)\n{\n   mPresent += step;\n   if (mPresent > mMax)\n      mPresent = mMin;\n   redraw();\n}\n\nProgressBar::ProgressBar(int x, int y, int w, int h, const char *lbl)\n:  Fl_Box(x, y, w, h, lbl)\n{\n   mMin = mPresent = 0;\n   mMax = 100;\n   mShowPct = true;\n   mShowMsg = false;\n   box(FL_THIN_UP_BOX);\n   color(FL_WHITE);\n}\n\nvoid ProgressBar::draw()\n{\n   struct Rectangle {\n      int x, y, w, h;\n   };\n\n   //drawstyle(style(), flags());\n   draw_box();\n   Rectangle r = {x(), y(), w(), h()};\n   if (mPresent > mMax)\n      mPresent = mMax;\n   if (mPresent < mMin)\n      mPresent = mMin;\n   double pct = (mPresent - mMin) / mMax;\n\n   r.w = r.w * pct + .5;\n   fl_rectf(r.x, r.y, r.w, r.h, FL_BLUE);\n\n   if (mShowMsg) {\n      fl_color(FL_RED);\n      fl_font(this->labelfont(), this->labelsize());\n      fl_draw(mMsg, x(), y(), w(), h(), FL_ALIGN_CENTER);\n   } else if (mShowPct) {\n      char buffer[30];\n      sprintf(buffer, \"%d%%\", int (pct * 100 + .5));\n      fl_color(FL_RED);\n      fl_font(this->labelfont(), this->labelsize());\n      fl_draw(buffer, x(), y(), w(), h(), FL_ALIGN_CENTER);\n   }\n}\n\n\n// Download-item class -------------------------------------------------------\n\nstatic void prButton_scb(Fl_Widget *, void *cb_data)\n{\n   DLItem *i = (DLItem *)cb_data;\n\n   i->prButton_cb();\n}\n\nDLItem::DLItem(const char *full_filename, const char *url, const char *user_agent)\n{\n   struct stat ss;\n   const char *p;\n   char *esc_url;\n\n   if (pipe(LogPipe) < 0) {\n      MSG(\"pipe, %s\\n\", dStrerror(errno));\n      return;\n   }\n   /* Set FD to background */\n   fcntl(LogPipe[0], F_SETFL,\n         O_NONBLOCK | fcntl(LogPipe[0], F_GETFL));\n\n   fullname = dStrdup(full_filename);\n   p = strrchr(fullname, '/');\n   shortname = (p) ? dStrdup(p + 1) : dStrdup(\"??\");\n   p = strrchr(full_filename, '/');\n   target_dir= p ? dStrndup(full_filename,p-full_filename+1) : dStrdup(\"??\");\n\n   useragent = dStrdup(user_agent);\n\n   log_len = 0;\n   log_max = 0;\n   log_state = ST_newline;\n   log_text = NULL;\n   onesec_bytesize = twosec_bytesize = curr_bytesize = init_bytesize = 0;\n   total_bytesize = -1;\n\n   // Init value. Reset later, upon the first data bytes arrival\n   init_time = time(NULL);\n\n   twosec_time = onesec_time = init_time;\n\n   // BUG:? test a URL with ' inside.\n   /* escape \"'\" character for the shell. Is it necessary? */\n   esc_url = Escape_uri_str(url, \"'\");\n\n   /* avoid malicious SMTP relaying with FTP urls */\n   if (dStrnAsciiCasecmp(esc_url, \"ftp:/\", 5) == 0)\n      Filter_smtp_hack(esc_url);\n\n   dl_argv = new char*[10];\n   int i = 0;\n   \n   if(dStrnAsciiCasecmp(esc_url, \"gemini:/\", 8) == 0) {\n      /* Use internal Gemini downloader */\n      dl_argv[i++] = (char*)DILLO_LIBDIR \"/dpi/gemini/gemini.filter.dpi\";\n      dl_argv[i++] = esc_url;\n      dl_argv[i++] = fullname;\n      /* ToDo: add the other options */\n      dl_argv[i++] = NULL;\n   } else if(dStrnAsciiCasecmp(esc_url, \"gopher:/\", 8) == 0) {\n      /* Use internal Gopher downloader */\n      dl_argv[i++] = (char*)DILLO_LIBDIR \"/dpi/gopher/gopher.filter.dpi\";\n      dl_argv[i++] = esc_url;\n      dl_argv[i++] = fullname;\n      /* ToDo: add the other options */\n      dl_argv[i++] = NULL;\n   } else {\n      /* Use external downloader */\n      dl_argv[i++] = (char*)DOWNLOADER_TOOL;\n      if (stat(fullname, &ss) == 0)\n         init_bytesize = (int)ss.st_size;\n      dl_argv[i++] = (char*)DOWNLOADER_USER_AGENT_ARG;\n      dl_argv[i++] = useragent;\n      dl_argv[i++] = (char*)DOWNLOADER_CONTINUE_ARG;\n      dl_argv[i++] = (char*)DOWNLOADER_LOAD_COOKIES_ARG;\n      dl_argv[i++] = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/cookies.txt\", NULL);\n      dl_argv[i++] = (char*)DOWNLOADER_OUTPUT_FILENAME_ARG;\n      dl_argv[i++] = fullname;\n      dl_argv[i++] = esc_url;\n      dl_argv[i++] = NULL;\n\n      // Create cookies.txt if it doesn't exist (needed for some downloaders)\n      FILE *fp = fopen(dl_argv[5], \"ab+\");\n      fclose(fp);\n   }\n\n   DataDone = 0;\n   LogDone = 0;\n   UpdatesDone = 0;\n   ForkDone = 0;\n   WidgetDone = 0;\n   DownloaderStatus = -1;\n\n   gw = 400, gh = 70;\n   group = new Fl_Group(0,0,gw,gh);\n   group->begin();\n   prTitle = new Fl_Box(24, 7, 290, 23);\n   prTitle->box(FL_RSHADOW_BOX);\n   prTitle->color(FL_WHITE);\n   prTitle->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_CLIP);\n   prTitle->copy_label(shortname);\n   // Attach this 'log_text' to the tooltip\n   log_text_add(\"Target File: \", 13);\n   log_text_add(fullname, strlen(fullname));\n   log_text_add(\"\\n\\n\", 2);\n\n   prBar = new ProgressBar(24, 40, 92, 20);\n   prBar->box(FL_THIN_UP_BOX);\n   prBar->tooltip(\"Progress Status\");\n\n   int ix = 122, iy = 37, iw = 50, ih = 14;\n   Fl_Widget *o = new Fl_Box(ix,iy,iw,ih, \"Got\");\n   o->box(FL_RFLAT_BOX);\n   o->color(FL_DARK2);\n   o->labelsize(12);\n   o->tooltip(\"Downloaded Size\");\n   prGot = new Fl_Box(ix,iy+14,iw,ih, \"0KB\");\n   prGot->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);\n   prGot->labelcolor(FL_BLUE);\n   prGot->labelsize(12);\n   prGot->box(FL_NO_BOX);\n\n   ix += iw;\n   o = new Fl_Box(ix,iy,iw,ih, \"Size\");\n   o->box(FL_RFLAT_BOX);\n   o->color(FL_DARK2);\n   o->labelsize(12);\n   o->tooltip(\"Total Size\");\n   prSize = new Fl_Box(ix,iy+14,iw,ih, \"??\");\n   prSize->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);\n   prSize->labelsize(12);\n   prSize->box(FL_NO_BOX);\n\n   ix += iw;\n   o = new Fl_Box(ix,iy,iw,ih, \"Rate\");\n   o->box(FL_RFLAT_BOX);\n   o->color(FL_DARK2);\n   o->labelsize(12);\n   o->tooltip(\"Current transfer Rate (KBytes/sec)\");\n   prRate = new Fl_Box(ix,iy+14,iw,ih, \"??\");\n   prRate->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);\n   prRate->labelsize(12);\n   prRate->box(FL_NO_BOX);\n\n   ix += iw;\n   o = new Fl_Box(ix,iy,iw,ih, \"~Rate\");\n   o->box(FL_RFLAT_BOX);\n   o->color(FL_DARK2);\n   o->labelsize(12);\n   o->tooltip(\"Average transfer Rate (KBytes/sec)\");\n   pr_Rate = new Fl_Box(ix,iy+14,iw,ih, \"??\");\n   pr_Rate->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);\n   pr_Rate->labelsize(12);\n   pr_Rate->box(FL_NO_BOX);\n\n   ix += iw;\n   prETAt = o = new Fl_Box(ix,iy,iw,ih, \"ETA\");\n   o->box(FL_RFLAT_BOX);\n   o->color(FL_DARK2);\n   o->labelsize(12);\n   o->tooltip(\"Estimated Time of Arrival\");\n   prETA = new Fl_Box(ix,iy+14,iw,ih, \"??\");\n   prETA->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);\n   prETA->labelsize(12);\n   prETA->box(FL_NO_BOX);\n\n   prButton = new Fl_Button(326, 9, 44, 19, \"Stop\");\n   prButton->tooltip(\"Stop this transfer\");\n   prButton->box(FL_UP_BOX);\n   prButton->clear_visible_focus();\n   prButton->callback(prButton_scb, this);\n\n   group->box(FL_ROUNDED_BOX);\n   group->end();\n}\n\nDLItem::~DLItem()\n{\n   free(shortname);\n   dFree(fullname);\n   dFree(useragent);\n   dFree(target_dir);\n   free(log_text);\n   int idx = (strcmp(dl_argv[1], \"-c\")) ? 2 : 3;\n   dFree(dl_argv[idx]);\n   dFree(dl_argv[idx+3]);\n   delete [] dl_argv;\n\n   delete(group);\n}\n\n/*\n * Abort a running download\n */\nvoid DLItem::abort_dl()\n{\n   if (!log_done()) {\n      dClose(LogPipe[0]);\n      Fl::remove_fd(LogPipe[0]);\n      log_done(1);\n      // Stop downloader\n      if (!fork_done())\n         kill(pid(), SIGTERM);\n   }\n   widget_done(1);\n}\n\nvoid DLItem::prButton_cb()\n{\n   prButton->deactivate();\n   abort_dl();\n}\n\nvoid DLItem::child_init()\n{\n   dClose(0); // stdin\n   dClose(1); // stdout\n   dClose(LogPipe[0]);\n   dup2(LogPipe[1], 2); // stderr\n   // set the locale to C for log parsing\n   setenv(\"LC_ALL\", \"C\", 1);\n   // start downloader\n   execvp(dl_argv[0], dl_argv);\n}\n\n/*\n * Update displayed size\n */\nvoid DLItem::update_prSize(int newsize)\n{\n   char num[64];\n\n   if (newsize > 1024 * 1024)\n      snprintf(num, 64, \"%.1fMB\", (float)newsize / (1024*1024));\n   else\n      snprintf(num, 64, \"%.0fKB\", (float)newsize / 1024);\n   prSize->copy_label(num);\n}\n\nvoid DLItem::log_text_add(const char *buf, ssize_t st)\n{\n   const char *p;\n   char *esc_str, *q, *d, num[64];\n   size_t esc_len;\n\n   // WORKAROUND: We have to escape '@' in FLTK tooltips.\n   esc_str = escape_tooltip(buf, st);\n   esc_len = strlen(esc_str);\n\n   // Make room...\n   if (log_len + esc_len >= log_max) {\n      log_max = log_len + esc_len + 1024;\n      log_text = (char *) dRealloc (log_text, log_max);\n      log_text[log_len] = 0;\n      prTitle->tooltip(log_text);\n   }\n\n   // FSM to remove downloader's \"dot-progress\" (i.e. \"^ \" || \"^[0-9]+K\")\n   q = log_text + log_len;\n   for (p = esc_str; (size_t)(p - esc_str) < esc_len; ++p) {\n      switch (log_state) {\n      case ST_newline:\n         if (*p == ' ') {\n            log_state = ST_discard;\n         } else if (isdigit(*p)) {\n            *q++ = *p; log_state = ST_number;\n         } else if (*p == '\\n') {\n            *q++ = *p;\n         } else {\n            *q++ = *p; log_state = ST_copy;\n         }\n         break;\n      case ST_number:\n         if (isdigit(*q++ = *p)) {\n            // keep here\n         } else if (*p == 'K') {\n            for (--q; isdigit(q[-1]); --q) ; \n            log_state = ST_discard;\n         } else {\n            log_state = ST_copy;\n         }\n         break;\n      case ST_discard:\n         if (*p == '\\n')\n            log_state = ST_newline;\n         break;\n      case ST_copy:\n         if ((*q++ = *p) == '\\n')\n            log_state = ST_newline;\n         break;\n      }\n   }\n   *q = 0;\n   log_len = strlen(log_text);\n\n   free(esc_str);\n\n   // Now scan for the length of the file\n   if (total_bytesize == -1) {\n      p = strstr(log_text, \"\\nLength: \");\n      if (p && isdigit(p[9]) && strchr(p + 9, ' ')) {\n         for (p += 9, d = &num[0]; *p != ' '; ++p)\n            if (isdigit(*p))\n               *d++ = *p;\n         *d = 0;\n         total_bytesize = strtol (num, NULL, 10);\n         // Update displayed size\n         update_prSize(total_bytesize);\n\n         // WORKAROUND: For unknown reasons a redraw is needed here for some\n         //             machines --jcid\n         group->redraw();\n      }\n   }\n\n   // Show we're connecting...\n   if (curr_bytesize == 0) {\n      prTitle->copy_label(\"Connecting...\");\n   }\n}\n\n///\nvoid DLItem::log_text_show()\n{\n   MSG(\"\\nStored Log:\\n%s\", log_text);\n}\n\nvoid DLItem::update_size(int new_sz)\n{\n   char buf[64];\n\n   if (curr_bytesize == 0 && new_sz) {\n      // Start the timer with the first bytes got\n      init_time = time(NULL);\n      // Update the title\n      prTitle->copy_label(shortname);\n      // WORKAROUND: For unknown reasons a redraw is needed here for some\n      //             machines --jcid\n      group->redraw();\n   }\n\n   curr_bytesize = new_sz;\n   if (curr_bytesize > 1024 * 1024)\n      snprintf(buf, 64, \"%.1fMB\", (float)curr_bytesize / (1024*1024));\n   else\n      snprintf(buf, 64, \"%.0fKB\", (float)curr_bytesize / 1024);\n   prGot->copy_label(buf);\n   if (total_bytesize == -1) {\n      prBar->showtext(false);\n      prBar->move(1);\n   } else {\n      prBar->showtext(true);\n      double pos = 100.0;\n      if (total_bytesize > 0)\n         pos *= (double)curr_bytesize / total_bytesize;\n      prBar->position(pos);\n   }\n}\n\nstatic void read_log_cb(int fd_in, void *data)\n{\n   DLItem *dl_item = (DLItem *)data;\n   const int BufLen = 4096;\n   char Buf[BufLen];\n   ssize_t st;\n\n   do {\n      st = read(fd_in, Buf, BufLen);\n      if (st < 0) {\n         if (errno == EAGAIN) {\n            break;\n         }\n         perror(\"read, \");\n         break;\n      } else if (st == 0) {\n         dClose(fd_in);\n         Fl::remove_fd(fd_in, 1);\n         dl_item->log_done(1);\n         break;\n      } else {\n         dl_item->log_text_add(Buf, st);\n      }\n   } while (1);\n}\n\nvoid DLItem::father_init()\n{\n   dClose(LogPipe[1]);\n   Fl::add_fd(LogPipe[0], 1, read_log_cb, this); // Read\n\n   // Start the timer after the child is running.\n   // (this makes a big difference with wget and other downloaders)\n   //init_time = time(NULL);\n}\n\n/*\n * Our downloader exited, let's check its status and update the panel.\n */\nvoid DLItem::child_finished(int status)\n{\n   downloader_status(status);\n\n   if (status == 0) {\n      prButton->label(\"Done\");\n      prButton->tooltip(\"Close this information panel\");\n   } else {\n      prButton->label(\"Close\");\n      prButton->tooltip(\"Close this information panel\");\n      status_msg(\"ABORTED\");\n      if (curr_bytesize == 0) {\n         // Update the title\n         prTitle->copy_label(shortname);\n      }\n   }\n   prButton->activate();\n   prButton->redraw();\n   MSG(DOWNLOADER_TOOL \" status %d\\n\", status);\n}\n\n/*\n * Convert seconds into human readable [hour]:[min]:[sec] string.\n */\nstatic void secs2timestr(int et, char *str)\n{\n   int eh, em, es;\n\n   eh = et / 3600; em = (et % 3600) / 60; es = et % 60;\n   if (eh == 0) {\n      if (em == 0)\n         snprintf(str, 8, \"%ds\", es);\n      else\n         snprintf(str, 8, \"%dm%ds\", em, es);\n   } else {\n      snprintf(str, 8, \"%dh%dm\", eh, em);\n   }\n}\n\n/*\n * Update Got, Rate, ~Rate and ETA\n */\nvoid DLItem::update()\n{\n   struct stat ss;\n   time_t curr_time;\n   float csec, tsec, rate, _rate = 0;\n   char str[64];\n   int et;\n\n   if (updates_done())\n      return;\n\n   /* Update curr_size */\n   if (stat(fullname, &ss) == -1) {\n      MSG(\"stat, %s\\n\", dStrerror(errno));\n      return;\n   }\n   update_size((int)ss.st_size);\n\n   /* Get current time */\n   time(&curr_time);\n   csec = (float) (curr_time - init_time);\n\n   /* Rate */\n   if (csec >= 2) {\n      tsec = (float) (curr_time - twosec_time);\n      rate = ((float)(curr_bytesize-twosec_bytesize) / 1024) / tsec;\n      snprintf(str, 64, (rate < 100) ? \"%.1fK/s\" : \"%.0fK/s\", rate);\n      prRate->copy_label(str);\n   }\n   /* ~Rate */\n   if (csec >= 1) {\n      _rate = ((float)(curr_bytesize-init_bytesize) / 1024) / csec;\n      snprintf(str, 64, (_rate < 100) ? \"%.1fK/s\" : \"%.0fK/s\", _rate);\n      pr_Rate->copy_label(str);\n   }\n\n   /* ETA */\n   if (fork_done()) {\n      updates_done(1); // Last update\n      prETAt->label(\"Time\");\n      prETAt->tooltip(\"Download Time\");\n      prETAt->redraw();\n      secs2timestr((int)csec, str);\n      prETA->copy_label(str);\n      if (total_bytesize == -1) {\n         update_prSize(curr_bytesize);\n         if (downloader_status() == 0)\n            status_msg(\"Done\");\n      }\n   } else {\n      if (_rate > 0 && total_bytesize > 0 && curr_bytesize > 0) {\n         et = (int)((total_bytesize-curr_bytesize) / (_rate * 1024));\n         secs2timestr(et, str);\n         prETA->copy_label(str);\n      }\n   }\n\n   /* Update one and two secs ago times and bytesizes */\n   twosec_time = onesec_time;\n   onesec_time = curr_time;\n   twosec_bytesize = onesec_bytesize;\n   onesec_bytesize = curr_bytesize;\n}\n\n// SIGCHLD -------------------------------------------------------------------\n\n/*! SIGCHLD handler\n */\nstatic void raw_sigchld(int)\n{\n   caught_sigchld = 1;\n}\n\n/*! Establish SIGCHLD handler */\nstatic void est_sigchld(void)\n{\n   struct sigaction sigact;\n   sigset_t set;\n\n   (void) sigemptyset(&set);\n   sigact.sa_handler = raw_sigchld;\n   sigact.sa_mask = set;\n   sigact.sa_flags = SA_NOCLDSTOP;\n   if (sigaction(SIGCHLD, &sigact, NULL) == -1) {\n      perror(\"sigaction\");\n      exit(1);\n   }\n}\n\n/*\n * Timeout function to check downloader's exit status.\n */\nstatic void cleanup_cb(void *data)\n{\n   DLItemList *list = (DLItemList *)data;\n\n   sigprocmask(SIG_BLOCK, &mask_sigchld, NULL);\n   if (caught_sigchld) {\n      /* Handle SIGCHLD */\n      int i, status;\n      for (i = 0; i < list->num(); ++i) {\n         if (!list->get(i)->fork_done() &&\n             waitpid(list->get(i)->pid(), &status, WNOHANG) > 0) {\n            list->get(i)->child_finished(status);\n            list->get(i)->fork_done(1);\n         }\n      }\n      caught_sigchld = 0;\n   }\n   sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);\n\n   Fl::repeat_timeout(1.0,cleanup_cb,data);\n}\n\n/*\n * Timeout function to update the widget indicators,\n * also remove widgets marked \"done\".\n */\nstatic void update_cb(void *data)\n{\n   static int cb_used = 0;\n\n   DLItemList *list = (DLItemList *)data;\n\n   /* Update the widgets and remove the ones marked as done */\n   for (int i = 0; i < list->num(); ++i) {\n      if (!list->get(i)->widget_done()) {\n         list->get(i)->update();\n      } else if (list->get(i)->fork_done()) {\n         // widget_done and fork_done avoid a race condition.\n         dl_win->del(i); --i;\n      }\n      cb_used = 1;\n   }\n\n   if (cb_used && list->num() == 0)\n      exit(0);\n\n   Fl::repeat_timeout(1.0,update_cb,data);\n}\n\n\n// DLWin ---------------------------------------------------------------------\n\n/*\n * Callback function for the request socket.\n * Read the request, parse and start a new download.\n */\nstatic void read_req_cb(int req_fd, void *)\n{\n   struct sockaddr_un clnt_addr;\n   int sock_fd;\n   socklen_t csz;\n   Dsh *sh = NULL;\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *user_agent = NULL, *dl_dest = NULL;\n\n   /* Initialize the value-result parameter */\n   csz = sizeof(struct sockaddr_un);\n   /* accept the request */\n   do {\n      sock_fd = accept(req_fd, (struct sockaddr *) &clnt_addr, &csz);\n   } while (sock_fd == -1 && errno == EINTR);\n   if (sock_fd == -1) {\n      MSG(\"accept, %s fd=%d\\n\", dStrerror(errno), req_fd);\n      return;\n   }\n\n   /* create a sock handler */\n   sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);\n\n   /* Authenticate our client... */\n   if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||\n       a_Dpip_check_auth(dpip_tag) < 0) {\n      MSG(\"can't authenticate request: %s fd=%d\\n\", dStrerror(errno), sock_fd);\n      a_Dpip_dsh_close(sh);\n      goto end;\n   }\n   dFree(dpip_tag);\n\n   /* Read request */\n   if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1))) {\n      MSG(\"can't read request: %s fd=%d\\n\", dStrerror(errno), sock_fd);\n      a_Dpip_dsh_close(sh);\n      goto end;\n   }\n   a_Dpip_dsh_close(sh);\n   _MSG(\"Received tag={%s}\\n\", dpip_tag);\n\n   if ((cmd = a_Dpip_get_attr(dpip_tag, \"cmd\")) == NULL) {\n      MSG(\"Failed to parse 'cmd' in {%s}\\n\", dpip_tag);\n      goto end;\n   }\n   if (strcmp(cmd, \"DpiBye\") == 0) {\n      MSG(\"got DpiBye, ignoring...\\n\");\n      goto end;\n   }\n   if (strcmp(cmd, \"download\") != 0) {\n      MSG(\"unknown command: '%s'. Aborting.\\n\", cmd);\n      goto end;\n   }\n   if (!(user_agent = a_Dpip_get_attr(dpip_tag, \"user_agent\"))){\n      MSG(\"Failed to parse 'user_agent' in {%s}\\n\", dpip_tag);\n      goto end;\n   }\n   if (!(url = a_Dpip_get_attr(dpip_tag, \"url\"))){\n      MSG(\"Failed to parse 'url' in {%s}\\n\", dpip_tag);\n      goto end;\n   }\n   if (!(dl_dest = a_Dpip_get_attr(dpip_tag, \"destination\"))){\n      MSG(\"Failed to parse 'destination' in {%s}\\n\", dpip_tag);\n      goto end;\n   }\n   dl_win->add(dl_dest, url, user_agent);\n\nend:\n   dFree(cmd);\n   dFree(url);\n   dFree(user_agent);\n   dFree(dl_dest);\n   dFree(dpip_tag);\n   a_Dpip_dsh_free(sh);\n}\n\n/*\n * Callback for close window request (WM or EscapeKey press)\n */\nstatic void dlwin_esc_cb(Fl_Widget *, void *)\n{\n   const char *msg = \"There are running downloads.\\n\"\n                     \"ABORT them and EXIT anyway?\";\n\n   if (dl_win && dl_win->num_running() > 0) {\n      fl_message_title(\"Dillo Downloads: Abort downloads?\");\n      int ch = fl_choice(\"%s\", \"Cancel\", \"*No\", \"Yes\", msg);\n      if (ch == 0 || ch == 1)\n         return;\n   }\n\n   /* abort each download properly */\n   dl_win->abort_all();\n}\n\n/*\n * Add a new download request to the main window and\n * fork a child to do the job.\n */\nvoid DLWin::add(const char *full_filename, const char *url, const char *user_agent)\n{\n   DLItem *dl_item = new DLItem(full_filename, url, user_agent);\n   mDList->add(dl_item);\n   mPG->insert(*dl_item->get_widget(), 0);\n\n   _MSG(\"Child index = %d\\n\", mPG->find(dl_item->get_widget()));\n\n   // Start the child process\n   pid_t f_pid = fork();\n   if (f_pid == 0) {\n      /* child */\n      dl_item->child_init();\n      _exit(EXIT_FAILURE);\n   } else if (f_pid < 0) {\n      perror(\"fork, \");\n      exit(1);\n   } else {\n      /* father */\n      dl_item->get_widget()->show();\n      dl_win->show();\n      dl_item->pid(f_pid);\n      dl_item->father_init();\n   }\n}\n\n/*\n * Delete a download request from the main window.\n */\nvoid DLWin::del(int n_item)\n{\n   DLItem *dl_item = mDList->get(n_item);\n\n   // Remove the widget from the packed group\n   mPG->remove(dl_item->get_widget());\n   mScroll->redraw();\n   mDList->del(n_item);\n   delete(dl_item);\n}\n\n/*\n * Return number of entries\n */\nint DLWin::num()\n{\n   return mDList->num();\n}\n\n/*\n * Return number of running downloads\n */\nint DLWin::num_running()\n{\n   int i, nr;\n\n   for (i = nr = 0; i < mDList->num(); ++i)\n      if (!mDList->get(i)->fork_done())\n         ++nr;\n   return nr;\n}\n\n/*\n * Set a callback function for the request socket\n */\nvoid DLWin::listen(int req_fd)\n{\n   Fl::add_fd(req_fd, 1, read_req_cb, NULL); // Read\n}\n\n/*\n * Abort each download properly, and let the main cycle exit\n */\nvoid DLWin::abort_all()\n{\n   for (int i = 0; i < mDList->num(); ++i)\n      mDList->get(i)->abort_dl();\n}\n\n/*\n * A Scroll class that resizes its resizable widget to its width.\n * see http://seriss.com/people/erco/fltk/#ScrollableWidgetBrowser\n */\nclass DlScroll : public Fl_Scroll\n{\npublic:\n  void resize(int x_, int y_, int w_, int h_)\n  {\n    Fl_Scroll::resize(x_, y_, w_, h_);\n    Fl_Widget *resizable_ = resizable();\n    int sb_size =\n       resizable_->h() <= h() ? 0 :\n       scrollbar_size() ? scrollbar_size() :\n       Fl::scrollbar_size();\n    if (resizable_)\n      resizable_->resize(resizable_->x(),\n                         resizable_->y(),\n                         w() - sb_size,\n                         resizable_->h());\n  }\n  DlScroll(int x, int y, int w, int h, const char *l = 0)\n    : Fl_Scroll(x, y, w, h, l)\n  {\n  }\n};\n\n/*\n * Create the main window and an empty list of requests.\n */\nDLWin::DLWin(int ww, int wh) {\n\n   // Init an empty list for the download requests\n   mDList = new DLItemList();\n\n   // Create the empty main window\n   mWin = new Fl_Window(ww, wh, \"Dillo Downloads\");\n   mWin->begin();\n   mScroll = new DlScroll(0,0,ww,wh);\n   mScroll->begin();\n   mPG = new Fl_Pack(0,0,ww-18,wh);\n   mPG->end();\n   mScroll->end();\n   mScroll->type(Fl_Scroll::VERTICAL);\n   mScroll->resizable(mPG);\n   mWin->end();\n   mWin->resizable(mScroll);\n   mWin->callback(dlwin_esc_cb, NULL);\n   mWin->show();\n\n   // Set SIGCHLD handlers\n   sigemptyset(&mask_sigchld);\n   sigaddset(&mask_sigchld, SIGCHLD);\n   est_sigchld();\n\n   fl_message_title_default(\"Dillo Downloads: Message\");\n\n   // Set the cleanup timeout\n   Fl::add_timeout(1.0, cleanup_cb, mDList);\n   // Set the update timeout\n   Fl::add_timeout(1.0, update_cb, mDList);\n}\n\n\n// ---------------------------------------------------------------------------\n\n/*\n * Set FL_NORMAL_LABEL to interpret neither symbols (@) nor shortcuts (&)\n */\nstatic void custLabelDraw(const Fl_Label* o, int X, int Y, int W, int H,\n                          Fl_Align align)\n{\n   const int interpret_symbols = 0;\n\n   fl_draw_shortcut = 0;\n   fl_font(o->font, o->size);\n   fl_color((Fl_Color)o->color);\n   fl_draw(o->value, X, Y, W, H, align, o->image, interpret_symbols);\n}\n\nstatic void custLabelMeasure(const Fl_Label* o, int& W, int& H)\n{\n   const int interpret_symbols = 0;\n\n   fl_draw_shortcut = 0;\n   fl_font(o->font, o->size);\n   fl_measure(o->value, W, H, interpret_symbols);\n}\n\n\n\n//int main(int argc, char **argv)\nint main()\n{\n   int ww = 420, wh = 85;\n\n   Fl::lock();\n\n   // Disable '@' and '&' interpretation in normal labels.\n   Fl::set_labeltype(FL_NORMAL_LABEL, custLabelDraw, custLabelMeasure);\n\n   Fl::scheme(NULL);\n\n   // Create the download window\n   dl_win = new DLWin(ww, wh);\n\n   // Start listening to the request socket\n   dl_win->listen(STDIN_FILENO);\n\n   MSG(\"started...\\n\");\n\n   return Fl::run();\n}\n\n"
  },
  {
    "path": "dpi/dpiutil.c",
    "content": "/*\n * File: dpiutil.c\n *\n * Copyright 2004-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n#include <unistd.h>\n#include <stdio.h>\n#include <stdarg.h>\n#include <string.h>\n#include <ctype.h>\n#include <errno.h>\n#include <sys/socket.h>\n\n#include \"dpiutil.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[dpiutil.c]: \" __VA_ARGS__)\n\n\n/* Escaping/De-escaping ---------------------------------------------------*/\n\n/*\n * Escape URI characters in 'esc_set' as %XX sequences.\n * Return value: New escaped string.\n */\nchar *Escape_uri_str(const char *str, const char *p_esc_set)\n{\n   static const char *esc_set, *hex = \"0123456789ABCDEF\";\n   char *p;\n   Dstr *dstr;\n   int i;\n\n   esc_set = (p_esc_set) ? p_esc_set : \"%#:' \";\n   dstr = dStr_sized_new(64);\n   for (i = 0; str[i]; ++i) {\n      if (str[i] <= 0x1F || str[i] == 0x7F || strchr(esc_set, str[i])) {\n         dStr_append_c(dstr, '%');\n         dStr_append_c(dstr, hex[(str[i] >> 4) & 15]);\n         dStr_append_c(dstr, hex[str[i] & 15]);\n      } else {\n         dStr_append_c(dstr, str[i]);\n      }\n   }\n   p = dstr->str;\n   dStr_free(dstr, FALSE);\n\n   return p;\n}\n\n/*\n * Unescape %XX sequences in a string.\n * Return value: a new unescaped string\n */\nchar *Unescape_uri_str(const char *s)\n{\n   char *p, *buf = dStrdup(s);\n\n   if (strchr(s, '%')) {\n      for (p = buf; (*p = *s); ++s, ++p) {\n         if (*p == '%' && isxdigit(s[1]) && isxdigit(s[2])) {\n            *p = (isdigit(s[1]) ? (s[1] - '0')\n                                : D_ASCII_TOUPPER(s[1]) - 'A' + 10) * 16;\n            *p += isdigit(s[2]) ? (s[2] - '0')\n                                : D_ASCII_TOUPPER(s[2]) - 'A' + 10;\n            s += 2;\n         }\n      }\n   }\n\n   return buf;\n}\n\n\nstatic const char *unsafe_chars = \"&<>\\\"'\";\nstatic const char *unsafe_rep[] =\n  { \"&amp;\", \"&lt;\", \"&gt;\", \"&quot;\", \"&#39;\" };\nstatic const int unsafe_rep_len[] =  { 5, 4, 4, 6, 5 };\n\n/*\n * Escape unsafe characters as html entities.\n * Return value: New escaped string.\n */\nchar *Escape_html_str(const char *str)\n{\n   int i;\n   char *p;\n   Dstr *dstr = dStr_sized_new(64);\n\n   for (i = 0; str[i]; ++i) {\n      if ((p = strchr(unsafe_chars, str[i])))\n         dStr_append(dstr, unsafe_rep[p - unsafe_chars]);\n      else\n         dStr_append_c(dstr, str[i]);\n   }\n   p = dstr->str;\n   dStr_free(dstr, FALSE);\n\n   return p;\n}\n\n/*\n * Unescape a few HTML entities (inverse of Escape_html_str)\n * Return value: New unescaped string.\n */\nchar *Unescape_html_str(const char *str)\n{\n   int i, j, k;\n   char *u_str = dStrdup(str);\n\n   if (!strchr(str, '&'))\n      return u_str;\n\n   for (i = 0, j = 0; str[i]; ++i) {\n      if (str[i] == '&') {\n         for (k = 0; k < 5; ++k) {\n            if (!dStrnAsciiCasecmp(str + i, unsafe_rep[k], unsafe_rep_len[k])) {\n               i += unsafe_rep_len[k] - 1;\n               break;\n            }\n         }\n         u_str[j++] = (k < 5) ? unsafe_chars[k] : str[i];\n      } else {\n         u_str[j++] = str[i];\n      }\n   }\n   u_str[j] = 0;\n\n   return u_str;\n}\n\n/*\n * Filter '\\n', '\\r', \"%0D\" and \"%0A\" from the authority part of an FTP url.\n * This helps to avoid a SMTP relaying hack. This filtering could be done\n * only when port == 25, but if the mail server is listening on another\n * port it wouldn't work.\n * Note: AFAIS this should be done by wget.\n */\nchar *Filter_smtp_hack(char *url)\n{\n   int i;\n   char c;\n\n   if (strlen(url) > 6) { /* ftp:// */\n      for (i = 6; (c = url[i]) && c != '/'; ++i) {\n         if (c == '\\n' || c == '\\r') {\n            memmove(url + i, url + i + 1, strlen(url + i));\n            --i;\n         } else if (c == '%' && url[i+1] == '0' &&\n                    (D_ASCII_TOLOWER(url[i+2]) == 'a' ||\n                     D_ASCII_TOLOWER(url[i+2]) == 'd')) {\n            memmove(url + i, url + i + 3, strlen(url + i + 2));\n            --i;\n         }\n      }\n   }\n   return url;\n}\n\n"
  },
  {
    "path": "dpi/dpiutil.h",
    "content": "/*\n * File: dpiutil.h\n *\n * Copyright 2004-2005 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n/*\n * This file contains common functions used by dpi programs.\n * (i.e. a convenience library).\n */\n\n#ifndef __DPIUTIL_H__\n#define __DPIUTIL_H__\n\n#include <stdio.h>\n#include \"d_size.h\"\n#include \"../dlib/dlib.h\"\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\n/*\n * Escape URI characters in 'esc_set' as %XX sequences.\n * Return value: New escaped string.\n */\nchar *Escape_uri_str(const char *str, const char *p_esc_set);\n\n/*\n * Unescape %XX sequences in a string.\n * Return value: a new unescaped string\n */\nchar *Unescape_uri_str(const char *str);\n\n/*\n * Escape unsafe characters as html entities.\n * Return value: New escaped string.\n */\nchar *Escape_html_str(const char *str);\n\n/*\n * Unescape a few HTML entities (inverse of Escape_html_str)\n * Return value: New unescaped string.\n */\nchar *Unescape_html_str(const char *str);\n\n/*\n * Filter an SMTP hack with a FTP URI\n */\nchar *Filter_smtp_hack(char *url);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __DPIUTIL_H__ */\n\n"
  },
  {
    "path": "dpi/file.c",
    "content": "/*\n * File: file.c :)\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Directory scanning is no longer streamed, but it gets sorted instead!\n * Directory entries on top, files next.\n * With new HTML layout.\n */\n\n#include <ctype.h>           /* for isspace */\n#include <errno.h>           /* for errno */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/select.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <dirent.h>\n#include <fcntl.h>\n#include <time.h>\n#include <signal.h>\n#include <netinet/in.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n#include \"d_size.h\"\n#include \"fileutil.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[file dpi]: \" __VA_ARGS__)\n#define _MSG_RAW(...)\n#define MSG_RAW(...)  printf(__VA_ARGS__)\n\n#define HIDE_DOTFILES TRUE\n\n/*\n * Communication flags\n */\n#define FILE_AUTH_OK     1     /* Authentication done */\n#define FILE_READ        2     /* Waiting data */\n#define FILE_WRITE       4     /* Sending data */\n#define FILE_DONE        8     /* Operation done */\n#define FILE_ERR        16     /* Operation error */\n\ntypedef enum {\n   st_start = 10,\n   st_dpip,\n   st_http,\n   st_content,\n   st_done,\n   st_err\n} FileState;\n\ntypedef struct {\n   Dsh *sh;\n   char *orig_url;\n   char *filename;\n   int file_fd;\n   off_t file_sz;\n   DilloDir *d_dir;\n   FileState state;\n   int err_code;\n   int flags;\n   int old_style;\n} ClientInfo;\n\n/*\n * Forward references\n */\nstatic const char *File_content_type(const char *filename);\n\n/*\n * Global variables\n */\nstatic int DPIBYE = 0;\nstatic int OLD_STYLE = 0;\n/* A list for the clients we are serving */\nstatic Dlist *Clients;\n/* Set of filedescriptors we're working on */\nfd_set read_set, write_set;\n\n/*\n * Close a file descriptor, but handling EINTR\n */\nstatic void File_close(int fd)\n{\n   while (fd >= 0 && close(fd) < 0 && errno == EINTR)\n      ;\n}\n\n/*\n * Allocate a DilloDir structure, set safe values in it and sort the entries.\n */\nstatic DilloDir *File_dillodir_fs_new(char *dirname)\n{\n   struct stat sb;\n   struct dirent *de;\n   DIR *dir;\n   DilloDir *Ddir;\n   char *full_path;\n\n   if (!(dir = opendir(dirname)))\n      return NULL;\n\n   Ddir = FileUtil_dillodir_new(dirname);\n\n   /* Scan every name and sort them */\n   while ((de = readdir(dir)) != 0) {\n      if (!strcmp(de->d_name, \".\") || !strcmp(de->d_name, \"..\"))\n         continue;              /* skip \".\" and \"..\" */\n\n      if (HIDE_DOTFILES) {\n         /* Don't add hidden files or backup files to the list */\n         if (de->d_name[0] == '.' ||\n             de->d_name[0] == '#' ||\n             (de->d_name[0] != '\\0' &&\n              de->d_name[strlen(de->d_name) - 1] == '~'))\n            continue;\n      }\n\n      full_path = dStrconcat(Ddir->dirname, de->d_name, NULL);\n      if (stat(full_path, &sb) == -1) {\n         dFree(full_path);\n         continue;              /* ignore files we can't stat */\n      }\n      \n      FileUtil_dillodir_add(Ddir, full_path, sb);\n   }\n\n   closedir(dir);\n\n   /* sort the entries */\n   dList_sort(Ddir->flist, (dCompareFunc)FileUtil_comp);\n\n   return Ddir;\n}\n\n/*\n * Based on the extension, return the content_type for the file.\n * (if there's no extension, analyze the data and try to figure it out)\n */\nstatic const char *File_content_type(const char *filename)\n{\n   int fd;\n   struct stat sb;\n   const char *ct;\n   char buf[256];\n   ssize_t buf_size;\n\n   if (!(ct = FileUtil_ext(filename))) {\n      /* everything failed, let's analyze the data... */\n      if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) != -1) {\n         if ((buf_size = read(fd, buf, 256)) == 256 ) {\n            ct = FileUtil_get_content_type_from_data(buf, (size_t)buf_size);\n\n         } else if (stat(filename, &sb) != -1 &&\n                    buf_size > 0 && buf_size == sb.st_size) {\n            ct = FileUtil_get_content_type_from_data(buf, (size_t)buf_size);\n         }\n         File_close(fd);\n      }\n   }\n   _MSG(\"File_content_type: name=%s ct=%s\\n\", filename, ct);\n   return ct;\n}\n\n/*\n * Send the HTML directory page in HTTP.\n */\nstatic void File_send_dir(ClientInfo *client)\n{\n   int n;\n   char *d_cmd;\n   const char *filecont;\n   FileInfo *finfo;\n   DilloDir *Ddir = client->d_dir;\n\n   if (client->state == st_start) {\n      /* Send DPI command */\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\",\n                               client->orig_url);\n      a_Dpip_dsh_write_str(client->sh, 1, d_cmd);\n      dFree(d_cmd);\n      client->state = st_dpip;\n\n   } else if (client->state == st_dpip) {\n      /* send HTTP header and HTML top part */\n\n      /* Send page title */\n      FileUtil_print_page_header(client->sh, \"file\", Ddir->dirname, client->old_style);\n\n      /* Output the parent directory */\n      FileUtil_print_parent_dir(client->sh, \"file\", Ddir->dirname);\n\n      /* HTML style toggle */\n      a_Dpip_dsh_write_str(client->sh, 0,\n         \"&nbsp;&nbsp;<a href='dpi:/file/toggle'>%</a>\\n\");\n\t \n      /* Output the file listing table */\n      FileUtil_print_table_header(client->sh, dList_length(Ddir->flist), client->old_style);\n\n      client->state = st_http;\n\n   } else if (client->state == st_http) {\n      /* send directories as HTML contents */\n      for (n = 0; n < dList_length(Ddir->flist); ++n) {\n         finfo = dList_nth_data(Ddir->flist,n);\n         filecont = File_content_type(finfo->full_path);\n         FileUtil_print_info(client->sh, finfo, n+1, filecont, client->old_style);\n      }\n      \n      FileUtil_print_table_footer(client->sh, dList_length(Ddir->flist), client->old_style);\n\n      FileUtil_print_page_footer(client->sh, client->old_style);\n\n      client->state = st_content;\n      client->flags |= FILE_DONE;\n   }\n}\n\n/*\n * Send an error page\n */\nstatic void File_prepare_send_error_page(ClientInfo *client, int res,\n                                         const char *orig_url)\n{\n   client->state = st_err;\n   client->err_code = res;\n   client->orig_url = dStrdup(orig_url);\n   client->flags &= ~FILE_READ;\n   client->flags |= FILE_WRITE;\n}\n\n/*\n * Send an error page\n */\nstatic void File_send_error_page(ClientInfo *client)\n{\n   const char *status;\n   char *d_cmd;\n   Dstr *body = dStr_sized_new(128);\n\n   if (client->err_code == EACCES) {\n      status = \"403 Forbidden\";\n   } else if (client->err_code == ENOENT) {\n      status = \"404 Not Found\";\n   } else {\n      /* good enough */\n      status = \"500 Internal Server Error\";\n   }\n   dStr_append(body, status);\n   dStr_append(body, \"\\n\");\n   dStr_append(body, dStrerror(client->err_code));\n\n   /* Send DPI command */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\",\n                            client->orig_url);\n   a_Dpip_dsh_write_str(client->sh, 0, d_cmd);\n   dFree(d_cmd);\n\n   a_Dpip_dsh_printf(client->sh, 0,\n                     \"HTTP/1.1 %s\\r\\n\"\n                     \"Content-Type: text/plain\\r\\n\"\n                     \"Content-Length: %d\\r\\n\"\n                     \"\\r\\n\"\n                     \"%s\",\n                     status, body->len, body->str);\n   dStr_free(body, TRUE);\n\n   client->flags |= FILE_DONE;\n}\n\n/*\n * Scan the directory, sort and prepare to send it enclosed in HTTP.\n */\nstatic int File_prepare_send_dir(ClientInfo *client,\n                                 const char *DirName, const char *orig_url)\n{\n   Dstr *ds_dirname;\n   DilloDir *Ddir;\n\n   /* Let's make sure this directory url has a trailing slash */\n   ds_dirname = dStr_new(DirName);\n   if (ds_dirname->str[ds_dirname->len - 1] != '/')\n      dStr_append(ds_dirname, \"/\");\n\n   /* Let's get a structure ready for transfer */\n   Ddir = File_dillodir_fs_new(ds_dirname->str);\n   dStr_free(ds_dirname, TRUE);\n   if (Ddir) {\n      /* looks ok, set things accordingly */\n      client->orig_url = dStrdup(orig_url);\n      client->d_dir = Ddir;\n      client->state = st_start;\n      client->flags &= ~FILE_READ;\n      client->flags |= FILE_WRITE;\n      return 0;\n   } else\n      return EACCES;\n}\n\n/*\n * Prepare to send HTTP headers and then the file itself.\n */\nstatic int File_prepare_send_file(ClientInfo *client,\n                                  const char *filename,\n                                  const char *orig_url)\n{\n   int fd, res = -1;\n   struct stat sb;\n\n   if (stat(filename, &sb) != 0) {\n      /* prepare a file-not-found error */\n      res = ENOENT;\n   } else if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) < 0) {\n      /* prepare an error message */\n      res = errno;\n   } else {\n      /* looks ok, set things accordingly */\n      client->file_fd = fd;\n      client->file_sz = sb.st_size;\n      client->d_dir = NULL;\n      client->state = st_start;\n      client->filename = dStrdup(filename);\n      client->orig_url = dStrdup(orig_url);\n      client->flags &= ~FILE_READ;\n      client->flags |= FILE_WRITE;\n      res = 0;\n   }\n   return res;\n}\n\n/*\n * Try to stat the file and determine if it's readable.\n */\nstatic void File_get(ClientInfo *client, const char *filename,\n                     const char *orig_url)\n{\n   int res;\n   struct stat sb;\n\n   if (stat(filename, &sb) != 0) {\n      /* stat failed, prepare a file-not-found error. */\n      res = ENOENT;\n   } else if (S_ISDIR(sb.st_mode)) {\n      /* set up for reading directory */\n      res = File_prepare_send_dir(client, filename, orig_url);\n   } else {\n      /* set up for reading a file */\n      res = File_prepare_send_file(client, filename, orig_url);\n   }\n   if (res != 0) {\n      File_prepare_send_error_page(client, res, orig_url);\n   }\n}\n\n/*\n * Send HTTP headers and then the file itself.\n */\nstatic int File_send_file(ClientInfo *client)\n{\n//#define LBUF 1\n#define LBUF 16*1024\n\n   const char *ct;\n   const char *unknown_type = \"application/octet-stream\";\n   char buf[LBUF], *d_cmd, *name;\n   int st, st2, namelen;\n   bool_t gzipped = FALSE;\n\n   if (client->state == st_start) {\n      /* Send DPI command */\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\",\n                               client->orig_url);\n      a_Dpip_dsh_write_str(client->sh, 1, d_cmd);\n      dFree(d_cmd);\n      client->state = st_dpip;\n\n   } else if (client->state == st_dpip) {\n      /* send HTTP header */\n\n      /* Check for gzipped file */\n      namelen = strlen(client->filename);\n      if (namelen > 3 &&\n          !dStrAsciiCasecmp(client->filename + namelen - 3, \".gz\")) {\n         gzipped = TRUE;\n         namelen -= 3;\n      }\n      /* Content-Type info is based on filename extension (with \".gz\" removed).\n       * If there's no known extension, perform data sniffing.\n       * If this doesn't lead to a conclusion, use \"application/octet-stream\".\n       */\n      name = dStrndup(client->filename, namelen);\n      if (!(ct = File_content_type(name)))\n         ct = unknown_type;\n      dFree(name);\n\n      /* Send HTTP headers */\n      a_Dpip_dsh_write_str(client->sh, 0, \"HTTP/1.1 200 OK\\r\\n\");\n      if (gzipped) {\n         a_Dpip_dsh_write_str(client->sh, 0, \"Content-Encoding: gzip\\r\\n\");\n      }\n      if (!gzipped || strcmp(ct, unknown_type)) {\n         a_Dpip_dsh_printf(client->sh, 0, \"Content-Type: %s\\r\\n\", ct);\n      } else {\n         /* If we don't know type for gzipped data, let dillo figure it out. */\n      }\n      a_Dpip_dsh_printf(client->sh, 1,\n                        \"Content-Length: %ld\\r\\n\"\n                        \"\\r\\n\",\n                        client->file_sz);\n      client->state = st_http;\n\n   } else if (client->state == st_http) {\n      /* Send body -- raw file contents */\n      if ((st = a_Dpip_dsh_tryflush(client->sh)) < 0) {\n         client->flags |= (st == -3) ? FILE_ERR : 0;\n      } else {\n         /* no pending data, let's send new data */\n         do {\n            st2 = read(client->file_fd, buf, LBUF);\n         } while (st2 < 0 && errno == EINTR);\n         if (st2 < 0) {\n            MSG(\"\\nERROR while reading from file '%s': %s\\n\\n\",\n                client->filename, dStrerror(errno));\n            client->flags |= FILE_ERR;\n         } else if (st2 == 0) {\n            client->state = st_content;\n            client->flags |= FILE_DONE;\n         } else {\n            /* ok to write */\n            st = a_Dpip_dsh_trywrite(client->sh, buf, st2);\n            client->flags |= (st == -3) ? FILE_ERR : 0;\n         }\n      }\n   }\n\n   return 0;\n}\n\n/*\n * Set the style flag and ask for a reload, so it shows immediately.\n */\nstatic void File_toggle_html_style(ClientInfo *client)\n{\n   char *d_cmd;\n\n   OLD_STYLE = !OLD_STYLE;\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s\", \"reload_request\");\n   a_Dpip_dsh_write_str(client->sh, 1, d_cmd);\n   dFree(d_cmd);\n}\n\n/*\n * Perform any necessary cleanups upon abnormal termination\n */\nstatic void termination_handler(int signum)\n{\n  MSG(\"\\nexit(signum), signum=%d\\n\\n\", signum);\n  exit(signum);\n}\n\n\n/* Client handling ----------------------------------------------------------*/\n\n/*\n * Add a new client to the list.\n */\nstatic ClientInfo *File_add_client(int sock_fd)\n{\n   ClientInfo *new_client;\n\n   new_client = dNew(ClientInfo, 1);\n   new_client->sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);\n   new_client->orig_url = NULL;\n   new_client->filename = NULL;\n   new_client->file_fd = -1;\n   new_client->file_sz = 0;\n   new_client->d_dir = NULL;\n   new_client->state = 0;\n   new_client->err_code = 0;\n   new_client->flags = FILE_READ;\n   new_client->old_style = OLD_STYLE;\n\n   dList_append(Clients, new_client);\n   return new_client;\n}\n\n/*\n * Remove a client from the list.\n */\nstatic void File_remove_client(ClientInfo *client)\n{\n   dList_remove(Clients, (void *)client);\n\n   _MSG(\"Closing Socket Handler\\n\");\n   a_Dpip_dsh_close(client->sh);\n   a_Dpip_dsh_free(client->sh);\n   File_close(client->file_fd);\n   dFree(client->orig_url);\n   dFree(client->filename);\n   FileUtil_dillodir_free(client->d_dir);\n\n   dFree(client);\n}\n\n/*\n * Serve this client.\n */\nstatic void File_serve_client(void *data, int f_write)\n{\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *path;\n   ClientInfo *client = data;\n   int st;\n\n   while (1) {\n      _MSG(\"File_serve_client %p, flags=%d state=%d\\n\",\n          client, client->flags, client->state);\n      if (client->flags & (FILE_DONE | FILE_ERR))\n         break;\n      if (client->flags & FILE_READ) {\n         dpip_tag = a_Dpip_dsh_read_token(client->sh, 0);\n         _MSG(\"dpip_tag={%s}\\n\", dpip_tag);\n         if (!dpip_tag)\n            break;\n      }\n\n      if (client->flags & FILE_READ) {\n         if (!(client->flags & FILE_AUTH_OK)) {\n            /* Authenticate our client... */\n            st = a_Dpip_check_auth(dpip_tag);\n            _MSG(\"a_Dpip_check_auth returned %d\\n\", st);\n            client->flags |= (st == 1) ? FILE_AUTH_OK : FILE_ERR;\n         } else {\n            /* Get file request */\n            cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n            url = a_Dpip_get_attr(dpip_tag, \"url\");\n            path = FileUtil_normalize_path(\"file\", url);\n            if (cmd) {\n               if (strcmp(cmd, \"DpiBye\") == 0) {\n                  DPIBYE = 1;\n                  MSG(\"(pid %d): Got DpiBye.\\n\", (int)getpid());\n                  client->flags |= FILE_DONE;\n               } else if (url && dStrnAsciiCasecmp(url, \"dpi:\", 4) == 0 &&\n                          strcmp(url+4, \"/file/toggle\") == 0) {\n                  File_toggle_html_style(client);\n               } else if (path) {\n                  File_get(client, path, url);\n               } else {\n                  client->flags |= FILE_ERR;\n                  MSG(\"ERROR: URL was %s\\n\", url);\n               }\n            }\n            dFree(path);\n            dFree(url);\n            dFree(cmd);\n            dFree(dpip_tag);\n            break;\n         }\n         dFree(dpip_tag);\n\n      } else if (f_write) {\n         /* send our answer */\n         if (client->state == st_err)\n            File_send_error_page(client);\n         else if (client->d_dir)\n            File_send_dir(client);\n         else\n            File_send_file(client);\n         break;\n      }\n   } /*while*/\n\n   client->flags |= (client->sh->status & DPIP_ERROR) ? FILE_ERR : 0;\n   client->flags |= (client->sh->status & DPIP_EOF) ? FILE_DONE : 0;\n}\n\n/*\n * Serve the client queue.\n */\nstatic void File_serve_clients()\n{\n   int i, f_read, f_write;\n   ClientInfo *client;\n\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      f_read = FD_ISSET(client->sh->fd_in, &read_set);\n      f_write = FD_ISSET(client->sh->fd_out, &write_set);\n      if (!f_read && !f_write)\n         continue;\n      File_serve_client(client, f_write);\n      if (client->flags & (FILE_DONE | FILE_ERR)) {\n         File_remove_client(client);\n         --i;\n      }\n   }\n}\n\n/* --------------------------------------------------------------------------*/\n\n/*\n * Check the fd sets for activity, with a max timeout.\n * return value: 0 if timeout, 1 if input available, -1 if error.\n */\nstatic int File_check_fds(uint_t seconds)\n{\n   int i, st;\n   ClientInfo *client;\n   struct timeval timeout;\n\n   /* initialize observed file descriptors */\n   FD_ZERO (&read_set);\n   FD_ZERO (&write_set);\n   FD_SET (STDIN_FILENO, &read_set);\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      if (client->flags & FILE_READ)\n         FD_SET (client->sh->fd_in, &read_set);\n      if (client->flags & FILE_WRITE)\n         FD_SET (client->sh->fd_out, &write_set);\n   }\n   _MSG(\"Watching %d fds\\n\", dList_length(Clients) + 1);\n\n   /* Initialize the timeout data structure. */\n   timeout.tv_sec = seconds;\n   timeout.tv_usec = 0;\n\n   do {\n      st = select(FD_SETSIZE, &read_set, &write_set, NULL, &timeout);\n   } while (st == -1 && errno == EINTR);\n/*\n   MSG_RAW(\" (%d%s%s)\", STDIN_FILENO,\n           FD_ISSET(STDIN_FILENO, &read_set) ? \"R\" : \"\",\n           FD_ISSET(STDIN_FILENO, &write_set) ? \"W\" : \"\");\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      MSG_RAW(\" (%d%s%s)\", client->sh->fd_in,\n              FD_ISSET(client->sh->fd_in, &read_set) ? \"R\" : \"\",\n              FD_ISSET(client->sh->fd_out, &write_set) ? \"W\" : \"\");\n   }\n   MSG_RAW(\"\\n\");\n*/\n   return st;\n}\n\n\nint main(void)\n{\n   struct sockaddr_in sin;\n   socklen_t sin_sz;\n   int sock_fd, c_st, st = 1;\n\n   /* Arrange the cleanup function for abnormal terminations */\n   if (signal (SIGINT, termination_handler) == SIG_IGN)\n     signal (SIGINT, SIG_IGN);\n   if (signal (SIGHUP, termination_handler) == SIG_IGN)\n     signal (SIGHUP, SIG_IGN);\n   if (signal (SIGTERM, termination_handler) == SIG_IGN)\n     signal (SIGTERM, SIG_IGN);\n\n   MSG(\"(v.2) accepting connections...\\n\");\n   //sleep(20);\n\n   /* initialize observed file descriptors */\n   FD_ZERO (&read_set);\n   FD_ZERO (&write_set);\n   FD_SET (STDIN_FILENO, &read_set);\n\n   /* Set STDIN socket nonblocking (to ensure accept() never blocks) */\n   fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK | fcntl(STDIN_FILENO, F_GETFL));\n\n   /* initialize Clients list */\n   Clients = dList_new(512);\n\n   /* some OSes may need this... */\n   sin_sz = sizeof(sin);\n\n   /* start the service loop */\n   while (!DPIBYE) {\n      /* wait for activity */\n      do {\n         c_st = File_check_fds(10);\n      } while (c_st == 0 && !DPIBYE);\n      if (c_st < 0) {\n         MSG(\" select() %s\\n\", dStrerror(errno));\n         break;\n      }\n      if (DPIBYE)\n         break;\n\n      if (FD_ISSET(STDIN_FILENO, &read_set)) {\n         /* accept the incoming connection */\n         do {\n            sock_fd = accept(STDIN_FILENO, (struct sockaddr *)&sin, &sin_sz);\n         } while (sock_fd < 0 && errno == EINTR);\n         if (sock_fd == -1) {\n            if (errno == EAGAIN)\n               continue;\n            MSG(\" accept() %s\\n\", dStrerror(errno));\n            break;\n         } else {\n            _MSG(\" accept() fd=%d\\n\", sock_fd);\n            /* Set nonblocking */\n            fcntl(sock_fd, F_SETFL, O_NONBLOCK | fcntl(sock_fd, F_GETFL));\n            /* Create and initialize a new client */\n            File_add_client(sock_fd);\n         }\n         continue;\n      }\n\n      File_serve_clients();\n   }\n\n   if (DPIBYE)\n      st = 0;\n   return st;\n}\n\n"
  },
  {
    "path": "dpi/fileutil.c",
    "content": "/*\n * File: fileutils.c :)\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <ctype.h>           /* for isspace */\n#include <errno.h>           /* for errno */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/time.h>\n#include <fcntl.h>\n\n#define __USE_XOPEN\n#include <time.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n#include \"d_size.h\"\n#include \"fileutil.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[file dpi]: \" __VA_ARGS__)\n#define _MSG_RAW(...)\n#define MSG_RAW(...)  printf(__VA_ARGS__)\n\n/*\n * Compare two FileInfo pointers\n * This function is used for sorting directories\n */\nint FileUtil_comp(const FileInfo *f1, const FileInfo *f2)\n{\n   if (S_ISDIR(f1->mode)) {\n      if (S_ISDIR(f2->mode)) {\n         return strcmp(f1->filename, f2->filename);\n      } else {\n         return -1;\n      }\n   } else {\n      if (S_ISDIR(f2->mode)) {\n         return 1;\n      } else {\n         return strcmp(f1->filename, f2->filename);\n      }\n   }\n}\n\n/*\n * Allocate an empty DilloDir structure.\n */\nDilloDir *FileUtil_dillodir_new(const char *dirname)\n{\n   DilloDir *Ddir;\n\n   Ddir = dNew(DilloDir, 1);\n   Ddir->dirname = dStrdup(dirname);\n   Ddir->flist = dList_new(512);\n\n   return Ddir;\n}\n\n/*\n * Add a FileInfo to DilloDir structure.\n */\nvoid FileUtil_dillodir_add(DilloDir *Ddir, char *full_path, struct stat sb)\n{\n   int dirname_len;\n   FileInfo *finfo;\n\n   dirname_len = strlen(Ddir->dirname);\n   \n   finfo = dNew(FileInfo, 1);\n   finfo->full_path = full_path;\n   finfo->filename = full_path + dirname_len;\n   finfo->size = sb.st_size;\n   finfo->mode = sb.st_mode;\n   finfo->mtime = sb.st_mtime;\n\n   dList_append(Ddir->flist, finfo);      \n}\n\n/*\n * Deallocate a DilloDir structure.\n */\nvoid FileUtil_dillodir_free(DilloDir *Ddir)\n{\n   int i;\n   FileInfo *finfo;\n\n   dReturn_if (Ddir == NULL);\n\n   for (i = 0; i < dList_length(Ddir->flist); ++i) {\n      finfo = dList_nth_data(Ddir->flist, i);\n      dFree(finfo->full_path);\n      dFree(finfo);\n   }\n\n   dList_free(Ddir->flist);\n   dFree(Ddir->dirname);\n   dFree(Ddir);\n}\n\n\n/*\n * Given a hex octet (e3, 2F, 20), return the corresponding\n * character if the octet is valid, and -1 otherwise\n */\nstatic int FileUtil_parse_hex_octet(const char *s)\n{\n   int hex_value;\n   char *tail, hex[3];\n\n   if ((hex[0] = s[0]) && (hex[1] = s[1])) {\n      hex[2] = 0;\n      hex_value = strtol(hex, &tail, 16);\n      if (tail - hex == 2)\n        return hex_value;\n   }\n\n   return -1;\n}\n\n/*\n * Extract the resource part of an URI.\n */\nchar *FileUtil_get_resource(const char *dpiname, const char *orig)\n{\n   char *str = (char *) orig;\n\n   dReturn_val_if (orig == NULL, NULL);\n\n   /* Make sure the string starts with dpiname + \":\" */\n   if (dStrnAsciiCasecmp(str, dpiname, strlen(dpiname)) != 0)\n      return NULL;\n   str += strlen(dpiname);\n   \n   if (dStrnAsciiCasecmp(str, \":\", 1) != 0)\n      return NULL;\n\n   str += 1;\n   \n   return dStrdup(str);\n}\n\n/*\n * Make a file URL into a human (and machine) readable path.\n * The idea is to always have a path that starts with only one slash.\n * Embedded slashes are ignored.\n */\nchar *FileUtil_normalize_path(const char *dpiname, const char *orig)\n{\n   char *str = (char *) orig, *basename = NULL, *ret = NULL, *p;\n\n   dReturn_val_if (orig == NULL, ret);\n\n   /* Make sure the string starts with dpiname + \":/\" */\n   if (dStrnAsciiCasecmp(str, dpiname, strlen(dpiname)) != 0)\n      return ret;\n   str += strlen(dpiname);\n   \n   if (dStrnAsciiCasecmp(str, \":/\", 2) != 0)\n      return ret;\n   str += 1;\n\n   /* Skip \"localhost\" */\n   if (dStrnAsciiCasecmp(str, \"//localhost/\", 12) == 0)\n      str += 11;\n\n   /* Skip packed slashes, and leave just one */\n   while (str[0] == '/' && str[1] == '/')\n      str++;\n\n   {\n      int i, val;\n      Dstr *ds = dStr_sized_new(32);\n      dStr_sprintf(ds, \"%s%s%s\",\n                   basename ? basename : \"\",\n                   basename ? \"/\" : \"\",\n                   str);\n      dFree(basename);\n\n      /* Parse possible hexadecimal octets in the URI path */\n      for (i = 0; ds->str[i]; ++i) {\n         if (ds->str[i] == '%' &&\n             (val = FileUtil_parse_hex_octet(ds->str + i+1)) != -1) {\n            ds->str[i] = val;\n            dStr_erase(ds, i+1, 2);\n         }\n      }\n      /* Remove the fragment if not a part of the filename */\n      if ((p = strrchr(ds->str, '#')) != NULL && access(ds->str, F_OK) != 0)\n         dStr_truncate(ds, p - ds->str);\n      ret = ds->str;\n      dStr_free(ds, 0);\n   }\n\n   return ret;\n}\n \n/*\n * Detects 'Content-Type' when the server does not supply one.\n * It uses the magic(5) logic from file(1). Currently, it\n * only checks the few mime types that Dillo supports.\n *\n * 'Data' is a pointer to the first bytes of the raw data.\n * (this is based on a_Misc_get_content_type_from_data())\n */\nconst char *FileUtil_get_content_type_from_data(void *Data, size_t Size)\n{\n   static const char *Types[] = {\n      \"application/octet-stream\",\n      \"text/html\", \"text/plain\",\n      \"image/gif\", \"image/png\", \"image/jpeg\",\n      \"application/zip\"\n   };\n   int Type = 0;\n   char *p = Data;\n   size_t i, non_ascci;\n\n   _MSG(\"FileUtil_get_content_type_from_data:: Size = %d\\n\", Size);\n\n   /* HTML try */\n   for (i = 0; i < Size && dIsspace(p[i]); ++i);\n   if ((Size - i >= 5  && !dStrnAsciiCasecmp(p+i, \"<html\", 5)) ||\n       (Size - i >= 5  && !dStrnAsciiCasecmp(p+i, \"<head\", 5)) ||\n       (Size - i >= 6  && !dStrnAsciiCasecmp(p+i, \"<title\", 6)) ||\n       (Size - i >= 14 && !dStrnAsciiCasecmp(p+i, \"<!doctype html\", 14)) ||\n       /* this line is workaround for FTP through the Squid proxy */\n       (Size - i >= 17 && !dStrnAsciiCasecmp(p+i, \"<!-- HTML listing\", 17))) {\n\n      Type = 1;\n\n   /* Images */\n   } else if (Size >= 4 && !strncmp(p, \"GIF8\", 4)) {\n      Type = 3;\n   } else if (Size >= 4 && !strncmp(p, \"\\x89PNG\", 4)) {\n      Type = 4;\n   } else if (Size >= 2 && !strncmp(p, \"\\xff\\xd8\", 2)) {\n      /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking\n       * at the character representation should be machine independent. */\n      Type = 5;\n\n   /* Compressed */\n   } else if (Size >= 4 && (!strncmp(p, \"PK\\x03\\x04\", 4) ||\n                            !strncmp(p, \"PK\\x05\\x06\", 4) ||\n                            !strncmp(p, \"PK\\x07\\x08\", 4))) {\n      Type = 6;\n\n   /* Text */\n   } else {\n      /* We'll assume \"text/plain\" if the set of chars above 127 is <= 10\n       * in a 256-bytes sample.  Better heuristics are welcomed! :-) */\n      non_ascci = 0;\n      Size = MIN (Size, 256);\n      for (i = 0; i < Size; i++)\n         if ((uchar_t) p[i] > 127)\n            ++non_ascci;\n      if (Size == 256) {\n         Type = (non_ascci > 10) ? 0 : 2;\n      } else {\n         Type = (non_ascci > 0) ? 0 : 2;\n      }\n   }\n\n   return (Types[Type]);\n}\n\n/*\n * Return a content type based on the extension of the filename.\n */\nconst char *FileUtil_ext(const char *filename)\n{\n   char *e;\n\n   if (!(e = strrchr(filename, '.')))\n      return NULL;\n\n   e++;\n\n   if (!dStrAsciiCasecmp(e, \"gif\")) {\n      return \"image/gif\";\n   } else if (!dStrAsciiCasecmp(e, \"jpg\") ||\n              !dStrAsciiCasecmp(e, \"jpeg\")) {\n      return \"image/jpeg\";\n   } else if (!dStrAsciiCasecmp(e, \"png\")) {\n      return \"image/png\";\n   } else if (!dStrAsciiCasecmp(e, \"html\") ||\n              !dStrAsciiCasecmp(e, \"htm\") ||\n              !dStrAsciiCasecmp(e, \"shtml\") ||\n              !dStrAsciiCasecmp(e, \"xhtml\")) {\n      return \"text/html\";\n   } else if (!dStrAsciiCasecmp(e, \"xml\") ||\n              !dStrAsciiCasecmp(e, \"ncx\") ||\n              !dStrAsciiCasecmp(e, \"opf\")) {\n      return \"text/xml\";\n   } else if (!dStrAsciiCasecmp(e, \"pdf\")) {\n      return \"application/pdf\";\n   } else if (!dStrAsciiCasecmp(e, \"zip\")) {\n      return \"application/zip\";\n   } else if (!dStrAsciiCasecmp(e, \"epub\")) {\n      return \"application/epub\";\n   } else if (!dStrAsciiCasecmp(e, \"rss\")) {\n      return \"application/rss+xml\";\n   } else if (!dStrAsciiCasecmp(e, \"gmi\")) {\n      return \"text/gemini\";\n   } else if (!dStrAsciiCasecmp(e, \"gophermap\")) {\n      return \"text/gopher\";\n   } else if (!dStrAsciiCasecmp(e, \"md\")) {\n      return \"text/markdown\";\n   } else if (!dStrAsciiCasecmp(e, \"js\")) {\n      return \"text/javascript\";\n   } else if (!dStrAsciiCasecmp(e, \"css\")) {\n      return \"text/css\";\n   } else if (!dStrAsciiCasecmp(e, \"txt\")) {\n      return \"text/plain\";\n   } else {\n      return NULL;\n   }\n}\n\n/*\n * Given a timestamp, output an HTML-formatted date string.\n */\nvoid FileUtil_print_mtime(Dsh *sh, time_t mtime, int old_style)\n{\n   char *ds = ctime(&mtime);\n\n   /* Month, day and {hour or year} */\n   if (old_style) {\n      a_Dpip_dsh_printf(sh, 0, \" %.3s %.2s\", ds + 4, ds + 8);\n      if (time(NULL) - mtime > 15811200) {\n         a_Dpip_dsh_printf(sh, 0, \"  %.4s\", ds + 20);\n      } else {\n         a_Dpip_dsh_printf(sh, 0, \" %.5s\", ds + 11);\n      }\n   } else {\n      a_Dpip_dsh_printf(sh, 0,\n         \"<td>%.3s&nbsp;%.2s&nbsp;%.5s\", ds + 4, ds + 8,\n         /* (more than 6 months old) ? year : hour; */\n         (time(NULL) - mtime > 15811200) ? ds + 20 : ds + 11);\n   }\n}\n\n/*\n * Output the HTML page header and title.\n */\nvoid FileUtil_print_page_header(Dsh *sh, const char *dpiname, const char *dirname, int old_style)\n{\n   char *Hdirname, *Udirname, *HUdirname;\n\n   /* Send page title */\n   Udirname = Escape_uri_str(dirname, NULL);\n   HUdirname = Escape_html_str(Udirname);\n   Hdirname = Escape_html_str(dirname);\n\n   a_Dpip_dsh_printf(sh, 0,\n      \"HTTP/1.1 200 OK\\r\\n\"\n      \"Content-Type: text/html\\r\\n\"\n      \"\\r\\n\"\n      \"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\\n\"\n      \"<HTML>\\n<HEAD>\\n <BASE href='%s:%s'>\\n\"\n      \" <TITLE>%s:%s</TITLE>\\n</HEAD>\\n\"\n      \"<BODY><H1>Directory listing of %s</H1>\\n\",\n      dpiname, HUdirname, dpiname, Hdirname, Hdirname);\n   dFree(Hdirname);\n   dFree(HUdirname);\n   dFree(Udirname);\n   \n   if (old_style) {\n      a_Dpip_dsh_write_str(sh, 0, \"<pre>\\n\");\n   }\n}\n\n/*\n * Output the string for parent directory.\n */\nvoid FileUtil_print_parent_dir(Dsh *sh, const char *dpiname, const char *dirname)\n{\n   if (strcmp(dirname, \"/\") != 0) {        /* Not the root dir */\n      char *p, *parent, *HUparent, *Uparent;\n\n      parent = dStrdup(dirname);\n      /* cut trailing slash */\n      parent[strlen(parent) - 1] = '\\0';\n      /* make 'parent' have the parent dir path */\n      if ((p = strrchr(parent, '/')))\n         *(p + 1) = '\\0';\n\n      Uparent = Escape_uri_str(parent, NULL);\n      HUparent = Escape_html_str(Uparent);\n      a_Dpip_dsh_printf(sh, 0,\n         \"<a href='%s:%s'>Parent directory</a>\", dpiname, HUparent);\n      dFree(HUparent);\n      dFree(Uparent);\n      dFree(parent);\n   }\n}\n\n/*\n * Output the header for the file listing table.\n */\nvoid FileUtil_print_table_header(Dsh *sh, int dirlen, int old_style)\n{\n   if (dirlen) {\n      if (old_style) {\n         a_Dpip_dsh_write_str(sh, 0, \"\\n\\n\");\n      } else {\n         a_Dpip_dsh_write_str(sh, 0,\n            \"<br><br>\\n\"\n            \"<table border=0 cellpadding=1 cellspacing=0\"\n            \" bgcolor=#E0E0E0 width=100%>\\n\"\n            \"<tr align=center>\\n\"\n            \"<td>\\n\"\n            \"<td width=60%><b>Filename</b>\"\n            \"<td><b>Type</b>\"\n            \"<td><b>Size</b>\"\n            \"<td><b>Modified&nbsp;at</b>\\n\");\n      }\n   } else {\n      a_Dpip_dsh_write_str(sh, 0, \"<br><br>Directory is empty...\");\n   }\n}\n\n/*\n * Output a HTML-line from file info.\n */\nvoid FileUtil_print_info(Dsh *sh, FileInfo *finfo, int n, const char *filecont, int old_style)\n{\n   int size;\n   char *sizeunits;\n   char namebuf[MAXNAMESIZE + 1];\n   char *Uref, *HUref, *Hname;\n   const char *ref, *name = finfo->filename;\n   const char *HHandler = \"\";\n\n   if (finfo->size <= 9999) {\n      size = finfo->size;\n      sizeunits = \"bytes\";\n   } else if (finfo->size / 1024 <= 9999) {\n      size = finfo->size / 1024 + (finfo->size % 1024 >= 1024 / 2);\n      sizeunits = \"KB\";\n   } else {\n      size = finfo->size / 1048576 + (finfo->size % 1048576 >= 1048576 / 2);\n      sizeunits = \"MB\";\n   }\n\n   /* we could note if it's a symlink... */\n   if (S_ISDIR (finfo->mode)) {\n      filecont = \"Directory\";\n   } else if (finfo->mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {\n      filecont = \"Executable\";\n   } else {\n      if (!filecont || !strcmp(filecont, \"application/octet-stream\"))\n         filecont = \"unknown\";\n   }\n   \n   /* dirty trick to open the file with the correct dpi */\n   if(!strcmp(filecont, \"application/zip\") ||\n      !strcmp(filecont, \"application/epub\")) {\n      HHandler = \"zip:\";\n   }\n\n   ref = finfo->full_path;\n\n   if (strlen(name) > MAXNAMESIZE) {\n      memcpy(namebuf, name, MAXNAMESIZE - 3);\n      strcpy(namebuf + (MAXNAMESIZE - 3), \"...\");\n      name = namebuf;\n   }\n\n   /* escape problematic filenames */\n   Uref = Escape_uri_str(ref, NULL);\n   HUref = Escape_html_str(Uref);\n   Hname = Escape_html_str(name);\n\n   if (old_style) {\n      char *dots = \".. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..\";\n      int ndots = MAXNAMESIZE - strlen(name);\n      a_Dpip_dsh_printf(sh, 0,\n         \"%s<a href='%s%s'>%s</a>\"\n         \" %s\"\n         \" %-11s%4d %-5s\",\n         S_ISDIR (finfo->mode) ? \">\" : \" \", HHandler, HUref, Hname[0] == '/' ? Hname + 1 : Hname,\n         dots + 50 - (ndots > 0 ? ndots : 0),\n         filecont, size, sizeunits);\n\n   } else {\n      a_Dpip_dsh_printf(sh, 0,\n         \"<tr align=center %s><td>%s<td align=left><a href='%s%s'>%s</a>\"\n         \"<td>%s<td>%d&nbsp;%s\",\n         (n & 1) ? \"bgcolor=#dcdcdc\" : \"\",\n         S_ISDIR (finfo->mode) ? \">\" : \" \", HHandler, HUref, Hname[0] == '/' ? Hname + 1 : Hname,\n         filecont, size, sizeunits);\n   }\n   FileUtil_print_mtime(sh, finfo->mtime, old_style);\n   a_Dpip_dsh_write_str(sh, 0, \"\\n\");\n\n   dFree(Hname);\n   dFree(HUref);\n   dFree(Uref);\n}\n\n/*\n * Output the footer for the file listing table.\n */\nvoid FileUtil_print_table_footer(Dsh *sh, int dirlen, int old_style)\n{\n   if (!old_style && dirlen) {\n      a_Dpip_dsh_write_str(sh, 0, \"</table>\\n\");\n   }\n}\n\n/*\n * Output the HTML page footer.\n */\nvoid FileUtil_print_page_footer(Dsh *sh, int old_style)\n{\n   if (old_style) {\n      a_Dpip_dsh_write_str(sh, 0, \"</pre>\\n\");\n   }\n\n   a_Dpip_dsh_write_str(sh, 1, \"</BODY></HTML>\\n\");\n}\n"
  },
  {
    "path": "dpi/fileutil.h",
    "content": "/*\n * File: fileutil.h\n *\n * Copyright 2004-2005 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n/*\n * This file contains common functions used by file-like dpi programs.\n * (i.e. a convenience library).\n */\n\n#ifndef __FILEUTIL_H__\n#define __FILEUTIL_H__\n\n#include <stdio.h>\n#include \"d_size.h\"\n#include \"../dlib/dlib.h\"\n\n#define MAXNAMESIZE 30\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\ntypedef struct {\n   char *dirname;\n   Dlist *flist;       /* List of files and subdirectories (for sorting) */\n} DilloDir;\n\ntypedef struct {\n   char *full_path;\n   const char *filename;\n   off_t size;\n   mode_t mode;\n   time_t mtime;\n} FileInfo;\n\n/*\n * Compare two FileInfo pointers\n * This function is used for sorting directories\n */\nint FileUtil_comp(const FileInfo *f1, const FileInfo *f2);\n\n/*\n * Allocate an empty DilloDir structure.\n */\nDilloDir *FileUtil_dillodir_new(const char *dirname);\n\n/*\n * Add a FileInfo to DilloDir structure.\n */\nvoid FileUtil_dillodir_add(DilloDir *Ddir, char *full_path, struct stat sb);\n\n/*\n * Deallocate a DilloDir structure.\n */\nvoid FileUtil_dillodir_free(DilloDir *Ddir);\n\n/*\n * Extract the resource part of an URI.\n */\nchar *FileUtil_get_resource(const char *dpiname, const char *orig);\n\n/*\n * Make a file URL into a human (and machine) readable path.\n * The idea is to always have a path that starts with only one slash.\n * Embedded slashes are ignored.\n */\nchar *FileUtil_normalize_path(const char *dpiname, const char *orig);\n\n/*\n * Detects 'Content-Type' when the server does not supply one.\n * It uses the magic(5) logic from file(1). Currently, it\n * only checks the few mime types that Dillo supports.\n *\n * 'Data' is a pointer to the first bytes of the raw data.\n * (this is based on a_Misc_get_content_type_from_data())\n */\nconst char *FileUtil_get_content_type_from_data(void *Data, size_t Size);\n\n/*\n * Return a content type based on the extension of the filename.\n */\nconst char *FileUtil_ext(const char *filename);\n\n/*\n * Given a timestamp, output an HTML-formatted date string.\n */\nvoid FileUtil_print_mtime(Dsh *sh, time_t mtime, int old_style);\n\n/*\n * Output the HTML page header and title.\n */\nvoid FileUtil_print_page_header(Dsh *sh, const char *dpiname, const char *dirname, int old_style);\n\n/*\n * Output the string for parent directory.\n */\nvoid FileUtil_print_parent_dir(Dsh *sh, const char *dpiname, const char *dirname);\n\n/*\n * Output the header for the file listing table.\n */\nvoid FileUtil_print_table_header(Dsh *sh, int dirlen, int old_style);\n\n/*\n * Output a HTML-line from file info.\n */\nvoid FileUtil_print_info(Dsh *sh, FileInfo *finfo, int n, const char *filecont, int old_style);\n\n/*\n * Output the footer for the file listing table.\n */\nvoid FileUtil_print_table_footer(Dsh *sh, int dirlen, int old_style);\n\n/*\n * Output the HTML page footer.\n */\nvoid FileUtil_print_page_footer(Dsh *sh, int old_style);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __FILEUTIL_H__ */\n\n"
  },
  {
    "path": "dpi/ftp.c",
    "content": "/*\n * Dpi for FTP.\n *\n * This server checks the ftp-URL to be a directory (requires wget).\n * If true, it sends back an html representation of it, and if not\n * a dpip message (which is caught by dillo who redirects the ftp URL\n * to the downloads server).\n *\n * Feel free to polish!\n *\n * Copyright 2003-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n/*\n * TODO:\n * - Send feedback about the FTP login process from wget's stderr.\n *   i.e. capture our child's stderr, process it, and report back.\n * - Handle simultaneous connections.\n *   If ftp.dpi is implemented with a low level ftp library, it becomes\n *   possible to keep the connection alive, and thus make browsing of ftp\n *   directories faster (this avoids one login per page, and forks). Perhaps\n *   it's not worth, but can be done.\n */\n\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n#include <sys/wait.h>\n#include <errno.h>\n#include <sys/time.h>\n#include <ctype.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n#include \"d_size.h\"\n\n/*\n * Debugging macros\n * (Set debugging messages to stderr, to see them)\n */\n#define _MSG(...)\n//#define MSG(...)  fprintf(stderr, \"[ftp dpi]: \" __VA_ARGS__)\n#define MSG(...)  printf(\"[ftp dpi]: \" __VA_ARGS__)\n\n/*\n * Global variables\n */\nstatic Dsh *sh = NULL;\nstatic char **dl_argv = NULL;\n\n/*---------------------------------------------------------------------------*/\n\n/* TODO: could use dStr ADT! */\ntypedef struct {\n   const char *str;\n   int len;\n} ContentType_t;\n\nstatic const ContentType_t MimeTypes[] = {\n   { \"application/octet-stream\", 24 },\n   { \"text/html\", 9 },\n   { \"text/plain\", 10 },\n   { \"image/gif\", 9 },\n   { \"image/png\", 9 },\n   { \"image/jpeg\", 10 },\n   { NULL, 0 }\n};\n\n/*\n * Detects 'Content-Type' from a data stream sample.\n *\n * It uses the magic(5) logic from file(1). Currently, it\n * only checks the few mime types that Dillo supports.\n *\n * 'Data' is a pointer to the first bytes of the raw data.\n *\n * Return value: (0 on success, 1 on doubt, 2 on lack of data).\n */\nstatic int a_Misc_get_content_type_from_data2(void *Data, size_t Size,\n                                              const char **PT)\n{\n   int st = 1;      /* default to \"doubt' */\n   int Type = 0;    /* default to \"application/octet-stream\" */\n   char *p = Data;\n   uchar_t ch;\n   size_t i, non_ascci;\n\n   /* HTML try */\n   for (i = 0; i < Size && dIsspace(p[i]); ++i);\n   if ((Size - i >= 5  && !dStrnAsciiCasecmp(p+i, \"<html\", 5)) ||\n       (Size - i >= 5  && !dStrnAsciiCasecmp(p+i, \"<head\", 5)) ||\n       (Size - i >= 6  && !dStrnAsciiCasecmp(p+i, \"<title\", 6)) ||\n       (Size - i >= 14 && !dStrnAsciiCasecmp(p+i, \"<!doctype html\", 14)) ||\n       /* this line is workaround for FTP through the Squid proxy */\n       (Size - i >= 17 && !dStrnAsciiCasecmp(p+i, \"<!-- HTML listing\", 17))) {\n\n      Type = 1;\n      st = 0;\n   /* Images */\n   } else if (Size >= 4 && !strncmp(p, \"GIF8\", 4)) {\n      Type = 3;\n      st = 0;\n   } else if (Size >= 4 && !strncmp(p, \"\\x89PNG\", 4)) {\n      Type = 4;\n      st = 0;\n   } else if (Size >= 2 && !strncmp(p, \"\\xff\\xd8\", 2)) {\n      /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking\n       * at the character representation should be machine independent. */\n      Type = 5;\n      st = 0;\n\n   /* Text */\n   } else {\n      /* We'll assume \"text/plain\" if the set of chars above 127 is <= 10%\n       * of the sample. This helps to catch ASCII, LATIN1 and UTF-8 as text.\n       * Better heuristics are welcomed! :-) */\n      non_ascci = 0;\n      Size = MIN (Size, 256);\n      for (i = 0; i < Size; i++) {\n         ch = (uchar_t) p[i];\n         if ((ch < 32 || ch > 126) && !dIsspace(ch))\n            ++non_ascci;\n      }\n      if (Size == 256) {\n         Type = (non_ascci > Size/10) ? 0 : 2;\n         st = 0;\n      } else {\n         Type = (non_ascci > Size/10) ? 0 : 2;\n      }\n   }\n\n   *PT = MimeTypes[Type].str;\n   return st;\n}\n\n/*---------------------------------------------------------------------------*/\n\n/*\n * Build a shell command using the ftp tool for this URL.\n */\nstatic void make_ftp_tool_argv(char *url)\n{\n   char *esc_url;\n\n   if (dl_argv) {\n      dFree(dl_argv[3]);\n      dFree(dl_argv);\n   }\n   dl_argv = dNew(char*, 10);\n\n   esc_url = Escape_uri_str(url, \"'\");\n   /* avoid malicious SMTP relaying with FTP urls */\n   Filter_smtp_hack(esc_url);\n\n#if(FTP_USE_WGET == 1)\n   dl_argv[0] = \"wget\";\n   dl_argv[1] = \"-t1\";  /* try once, default is 20 */\n   dl_argv[2] = \"-O-\";\n   dl_argv[3] = esc_url;\n   dl_argv[4] = NULL;\n#else\n   dl_argv[0] = \"ftp\";\n   dl_argv[1] = \"-r20\";  /* try once, default is 20 */\n   dl_argv[2] = \"-o-\";\n   dl_argv[3] = \"-a\";\n   dl_argv[4] = esc_url;\n   dl_argv[5] = NULL;\n#endif\n}\n\n/*\n * Fork, exec command, get its output and send via stdout.\n * Return: Number of bytes transfered, -1 if file-not_found, -2 if aborted.\n */\nstatic int try_ftp_transfer(char *url)\n{\n#define MIN_SZ 256\n#define READ_SZ 16*1024\n\n   ssize_t n;\n   int nb, has_mime_type, has_html_header, no_such_file, offer_download;\n   const char *mime_type = \"application/octet-stream\";\n   char buf[READ_SZ], *d_cmd;\n   Dstr *dbuf = dStr_sized_new(READ_SZ);\n   pid_t ch_pid;\n   int aborted = 0;\n   int DataPipe[2], InputPipe[2];\n\n   MSG(\"try_ftp_transfer: url=%s\\n\", url);\n\n   if (pipe(DataPipe) < 0) {\n      MSG(\"pipe, %s\\n\", dStrerror(errno));\n      return 0;\n   }\n   \n   if (pipe(InputPipe) < 0) {\n      MSG(\"pipe, %s\\n\", dStrerror(errno));\n      return 0;\n   }\n\n   /* Prepare args for execvp() */\n   make_ftp_tool_argv(url);\n\n   /* Start the child process */\n   if ((ch_pid = fork()) == 0) {\n      /* child */\n      /* start ftp cmd */\n  \t  close(InputPipe[1]);\n      dup2(InputPipe[0], 0); /* stdin */\n      dup2(DataPipe[1], 1); /* stdout */\n      execvp(dl_argv[0], dl_argv);\n      _exit(1);\n   } else if (ch_pid < 0) {\n      perror(\"fork, \");\n      exit(1);\n   } else {\n      /* father continues below */\n\t  close(InputPipe[0]);\n\t  write(InputPipe[1], \"ls\\n\", 3);\n\t  close(InputPipe[1]);\n\t  close(DataPipe[1]);\n   }\n\n   /* Read/Write the real data */\n   nb = 0;\n   has_mime_type = 0;\n   has_html_header = 0;\n   no_such_file = 0;\n   offer_download = 0;\n   do {\n      while ((n = read(DataPipe[0], buf, READ_SZ)) < 0 && errno == EINTR);\n      if (n > 0) {\n         dStr_append_l(dbuf, buf, n);\n         if (!has_mime_type && dbuf->len < MIN_SZ)\n            continue;\n      } else if (n < 0)\n         break;\n\n      if (!has_mime_type) {\n         if (dbuf->len == 0) {\n            /* When the file doesn't exist, the transfer size is zero */\n            no_such_file = 1;\n            break;\n         }\n         a_Misc_get_content_type_from_data2(dbuf->str, dbuf->len, &mime_type);\n         has_mime_type = 1;\n\n         if (strcmp(mime_type, \"application/octet-stream\") == 0) {\n            /* abort transfer */\n            kill(ch_pid, SIGTERM);\n            /* The \"application/octet-stream\" MIME type will be sent and\n             * Dillo will offer a download dialog */\n            offer_download = 1;\n            aborted = 1;\n         }\n      }\n\n      if (offer_download || (!aborted && !has_html_header && dbuf->len)) {\n         /* Send dpip tag */\n         d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n\n         /* Send HTTP header. */\n         a_Dpip_dsh_write_str(sh, 0, \"Content-type: \");\n#if(FTP_USE_WGET == 1)\n         a_Dpip_dsh_write_str(sh, 0, mime_type);\n         a_Dpip_dsh_write_str(sh, 1, \"\\r\\n\\r\\n\");\n#else\n         if(strlen(url) > 5 && url[strlen(url)-1] == '/') {\n            /* is a directory listing and we must generate\n               the HTML page for it */\n            a_Dpip_dsh_write_str(sh, 0, \"text/html\");\n            a_Dpip_dsh_write_str(sh, 1, \"\\r\\n\\r\\n\");\n            a_Dpip_dsh_write_str(sh, 1, \"<!DOCTYPE html>\");\n            a_Dpip_dsh_printf(sh, 1, \"<head><title>Index of %s</title></head>\", url);\n            a_Dpip_dsh_printf(sh, 1, \"<h1>Index of %s</h1>\", url);\n            a_Dpip_dsh_write_str(sh, 1, \"<hr>\");\n            a_Dpip_dsh_write_str(sh, 1, \"<pre>\");\n         } else {\n            a_Dpip_dsh_write_str(sh, 0, mime_type);\n            a_Dpip_dsh_write_str(sh, 1, \"\\r\\n\\r\\n\");\n         }\n#endif\n         has_html_header = 1;\n      }\n\n      if (!aborted && dbuf->len) {\n#if(FTP_USE_WGET == 1)\n         a_Dpip_dsh_write(sh, 1, dbuf->str, dbuf->len);\n# else\n         if(strlen(url) > 5 && url[strlen(url)-1] == '/') {\n            /* parse and reformat line */\n            int i, span, in_dir=0, fieldnum=0;\n            for(i=0; i<dbuf->len; i++) {\n               span = 0;\n\n               if(fieldnum==0 && dbuf->str[i] == 'd') {\n                  /* detect directory, needed later to assemble the href */\n                  in_dir=1;\n               } else if(dbuf->str[i] == ' ') {\n                  /* output spaces and increment field count */\n                  while(dbuf->str[i+span] == ' ' && i<dbuf->len) span++;\n                  a_Dpip_dsh_write(sh, 1, dbuf->str+i, span);\n                  i+=span;\n                  span=0;\n                  fieldnum++;\n               } else if(dbuf->str[i] == '\\n') {\n                  /* reset field count on newline */\n                  fieldnum=0;\n                  in_dir=0;\n               }\n\n               while(dbuf->str[i+span] != ' ' && dbuf->str[i+span] != '\\n' && i<dbuf->len) span++;\n\n               if(span) {\n                  /* output field */\n                  if(fieldnum==8) {\n                     /* last field is an href */\n                     while(dbuf->str[i+span] != '\\n' && i<dbuf->len) span++;\n                     a_Dpip_dsh_write(sh, 1, \"<a href=\\\"\", 9);\n                     a_Dpip_dsh_write(sh, 1, dbuf->str+i, span);\n                     if(in_dir) a_Dpip_dsh_write(sh, 1, \"/\", 1);\n                     a_Dpip_dsh_write(sh, 1, \"\\\">\", 2);\n                     a_Dpip_dsh_write(sh, 1, dbuf->str+i, span);\n                     if(in_dir) a_Dpip_dsh_write(sh, 1, \"/\", 1);\n                     a_Dpip_dsh_write(sh, 1, \"</a>\", 4);\n                  } else {\n                     /* normal field */\n                     a_Dpip_dsh_write(sh, 1, dbuf->str+i, span);\n                  }\n                  i+=span-1;\n               } else if(i<dbuf->len) {\n                  a_Dpip_dsh_write(sh, 1, dbuf->str+i, 1);\n               }\n            }\n         } else {\n            a_Dpip_dsh_write(sh, 1, dbuf->str, dbuf->len);\n         }\n#endif\n         nb += dbuf->len;\n         dStr_truncate(dbuf, 0);\n      }\n   } while (n > 0 && !aborted);\n\n#if(FTP_USE_WGET == 0)\n   if(strlen(url) > 5 && url[strlen(url)-1] == '/') {\n      /* close the generated HTML page */\n      a_Dpip_dsh_write_str(sh, 1, \"</pre>\");\n   }\n#endif\n\n   dStr_free(dbuf, 1);\n   return (no_such_file ? -1 : (aborted ? -2 : nb));\n}\n\n/*\n *\n */\nint main(int argc, char **argv)\n{\n   const char *err_msg = \"404 Not Found\\nNo such file or directory\";\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *url2 = NULL;\n   int st, rc;\n   char *p, *d_cmd;\n\n   /* ftp tool may need to write a temporary file... */\n   rc = chdir(\"/tmp\");\n   if (rc == -1) {\n      MSG(\"paths: error changing directory to /tmp: %s\\n\",\n          dStrerror(errno));\n   }\n\n   /* Initialize the SockHandler */\n   sh = a_Dpip_dsh_new(STDIN_FILENO, STDOUT_FILENO, 8*1024);\n\n   if (argc == 2) {\n      /* Debugging with a command line argument */\n      dpip_tag = dStrdup(argv[1]);\n   } else {\n      /* Authenticate our client... */\n      if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||\n          a_Dpip_check_auth(dpip_tag) < 0) {\n         MSG(\"can't authenticate request: %s\\n\", dStrerror(errno));\n         a_Dpip_dsh_close(sh);\n         return 1;\n      }\n      dFree(dpip_tag);\n      /* Read the dpi command from STDIN */\n      dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   }\n   MSG(\"tag=[%s]\\n\", dpip_tag);\n\n   cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n   url = a_Dpip_get_attr(dpip_tag, \"url\");\n   if (!cmd || !url) {\n      MSG(\"ERROR, cmd=%s, url=%s\\n\", cmd, url);\n      exit (EXIT_FAILURE);\n   }\n\n   if ((st = try_ftp_transfer(url)) == -1) {\n      /* Transfer failed, the requested file may not exist or be a symlink\n       * to a directory. Try again... */\n      if ((p = strrchr(url, '/')) && p[1] &&\n          p > url && p[-1] != '/') {\n         url2 = dStrconcat(url, \"/\", NULL);\n         st = try_ftp_transfer(url2);\n      }\n   }\n\n   if (st == -1) {\n      /* The transfer failed, let dillo know... */\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n      a_Dpip_dsh_write_str(sh, 0, d_cmd);\n      dFree(d_cmd);\n      a_Dpip_dsh_printf(sh, 1,\n                        \"HTTP/1.1 404 Not Found\\r\\n\"\n                        \"Content-Type: text/plain\\r\\n\"\n                        \"Content-Length: %d\\r\\n\"\n                        \"\\r\\n\"\n                        \"%s\",\n                        strlen(err_msg), err_msg);\n   }\n\n   dFree(cmd);\n   dFree(url);\n   dFree(url2);\n   dFree(dpip_tag);\n\n   /* Finish the SockHandler */\n   a_Dpip_dsh_close(sh);\n   a_Dpip_dsh_free(sh);\n\n   return 0;\n}\n\n"
  },
  {
    "path": "dpi/gemini.c",
    "content": "/*\n * Dpi for Gemini.\n *\n *\n *\n *                            W A R N I N G\n *\n * One of the important things to have in mind is about whether\n * unix domain sockets (UDS) are secure for gemini. I mean, root can always\n * snoop on sockets (regardless of permissions) so he'd be able to \"see\" all\n * the traffic. OTOH, if someone has root access on a machine he can do\n * anything, and that includes modifying the binaries, peeking-up in\n * memory space, installing a key-grabber, ...\n *\n *\n * Copyright 2003, 2004 Jorge Arellano Cid <jcid@dillo.org>\n * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * As a special exception permission is granted to link the code of\n * the gemini dillo plugin with the OpenSSL project's OpenSSL library\n * (or a modified version of that library), and distribute the linked\n * executables, without including the source code for the SSL library\n * in the source distribution. You must obey the GNU General Public\n * License, version 3, in all respects for all of the code used other\n * than the SSL library.\n *\n */\n\n/*\n * TODO: a lot of things, this is just a bare bones example.\n *\n * For instance:\n * - Certificate authentication (asking the user in case it can't be verified)\n * - Certificate management.\n * - Session caching ...\n *\n */\n\n#include <config.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <sys/un.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n#include <sys/wait.h>\n#include <errno.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n\n/*\n * Debugging macros\n */\n#define SILENT 0\n#define _MSG(...)\n#if SILENT\n #define MSG(...)\n#else\n #define MSG(...)  fprintf(stderr, \"[gemini dpi]: \" __VA_ARGS__)\n#endif\n\n#ifdef ENABLE_SSL\n\n#include <openssl/err.h>\n#include <openssl/rand.h>\n#include <openssl/ssl.h>\n\nstatic int get_network_connection(char * url);\nstatic int handle_certificate_problem(SSL * ssl_connection);\nstatic int save_certificate_home(X509 * cert);\n\n#endif\n\nchar servername[1024];\n\n/*---------------------------------------------------------------------------*/\n/*\n * Global variables\n */\nstatic char *root_url = NULL;  /*Holds the URL we are connecting to*/\nstatic Dsh *sh;\n\n\n#ifdef ENABLE_SSL\n\nstatic const char *const ca_files[] = {\n   \"/etc/ssl/certs/ca-certificates.crt\",\n   \"/etc/pki/tls/certs/ca-bundle.crt\",\n   \"/usr/share/ssl/certs/ca-bundle.crt\",\n   \"/usr/local/share/certs/ca-root.crt\",\n   \"/etc/ssl/cert.pem\",\n   CA_CERTS_FILE\n};\n\n/*\n * Read the answer dpip tag for a dialog and return the number for\n * the user-selected alternative.\n * Return: (-1: parse error, 0: window closed, 1-5 alt. number)\n */\nstatic int dialog_get_answer_number(void)\n{\n   int response_number = -1;\n   char *dpip_tag, *response;\n\n   /* Read the dpi command from STDIN */\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   response = a_Dpip_get_attr(dpip_tag, \"msg\");\n   response_number = (response) ? strtol (response, NULL, 10) : -1;\n   dFree(dpip_tag);\n   dFree(response);\n\n   return response_number;\n}\n\n/*\n *  This function read only a line from the SSL stream.\n */\nstatic int SSL_read_line(SSL *ssl, char *buf, int num)\n{\n   int c, r;\n\n   if(num)\n     buf[0] = '\\0';\n\n   for(c = 1; c <= num; c++) {\n      r = SSL_peek(ssl, buf, c);\n      if(r != c) break;\n      if(buf[c-1] == '\\n') break;\n   }\n   \n   return SSL_read(ssl, buf, c);\n}\n\n/*\n *  This function read until one of the chars is found from the SSL stream.\n */\nstatic int SSL_read_until_delims(SSL *ssl, char *buf, int num, const char *delims)\n{\n   int c, r;\n\n   if(num)\n     buf[0] = '\\0';\n\n   for(c = 1; c <= num; c++) {\n      r = SSL_peek(ssl, buf, c);\n      if(r != c) break;\n      if(strchr(delims, buf[c-1])) break;\n   }\n   \n   return SSL_read(ssl, buf, c);\n}\n\n/*\n *  This function initializes an SSL context\n */\nSSL_CTX *init_ssl(void)\n{\n   /* The following variable will be set to 1 in the event of\n    * an error and skip any further processing\n    */\n   int exit_error = 0;\n   SSL_CTX * ssl_context = NULL;\n   char buf[4096];\n   unsigned int u = 0;\n\n   /* Initialize library */\n   SSL_load_error_strings();\n   SSL_library_init();\n   if (RAND_status() != 1){\n      /*Insufficient entropy.  Deal with it?*/\n      MSG(\"Insufficient random entropy\\n\");\n   }\n\n   /* Create context and SSL object */\n   if (exit_error == 0){\n      ssl_context = SSL_CTX_new(SSLv23_client_method());\n      if (ssl_context == NULL){\n         MSG(\"Error creating SSL context\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* SSL2 has been known to be insecure forever, disabling SSL3 is in response\n    * to POODLE, and disabling compression is in response to CRIME.\n    */\n   if (exit_error == 0){\n      SSL_CTX_set_options(ssl_context,\n                          SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION);\n   }\n\n   /* Set directory to load certificates from */\n   /* FIXME - provide for sysconfdir variables and such */\n   if (exit_error == 0){\n      for (u = 0; u < sizeof(ca_files)/sizeof(ca_files[0]); u++) {\n         if (SSL_CTX_load_verify_locations(\n            ssl_context, ca_files[u], \"/etc/ssl/certs/\" ) == 0){\n            MSG(\"Error opening system x509 certificate location: %s\\n\", ca_files[u]);\n         }\n      }\n   }\n\n   if (exit_error == 0){\n      snprintf(buf, 4095, \"%s/.\" BINNAME \"/certs/\", dGethomedir());\n      if (SSL_CTX_load_verify_locations(ssl_context, NULL, buf )==0){\n         MSG(\"Error opening user x509 certificate location\\n\");\n         exit_error = 1;\n      }\n   }\n\n   if (exit_error == 0){\n      /* Don't want: eNULL, which has no encryption; aNULL, which has no\n       * authentication; LOW, which as of 2014 use 64 or 56-bit encryption;\n       * EXPORT40, which uses 40-bit encryption; RC4, for which methods were\n       * found in 2013 to defeat it somewhat too easily.\n       */\n      SSL_CTX_set_cipher_list(ssl_context,\n                            /*\n                              \"EECDH+AESGCM+AES128:EECDH+AESGCM+AES256:\"\n                              \"EECDH+CHACHA20:EDH+AESGCM+AES128:EDH+AESGCM+AES256:\"\n                              \"EDH+CHACHA20:EECDH+SHA256+AES128:EECDH+SHA384+AES256:\"\n                              \"EDH+SHA256+AES128:EDH+SHA256+AES256:EECDH+SHA1+AES128:\"\n                              \"EECDH+SHA1+AES256:EDH+SHA1+AES128:EDH+SHA1+AES256:\"\n                              \"EECDH+HIGH:EDH+HIGH:AESGCM+AES128:AESGCM+AES256:\"\n                              \"CHACHA20:SHA256+AES128:SHA256+AES256:SHA1+AES128:\"\n                              \"SHA1+AES256:HIGH:\"\n                              \"!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!aECDH\"\n                            */\n                              \"ALL:!aNULL:!eNULL:!LOW:!EXPORT40:!RC4:\");\n   }\n\n   return exit_error ? NULL : ssl_context;\n}\n\n/*\n * This function initializes an SSL connection\n */\nstatic SSL *init_ssl_connection(SSL_CTX *ssl_context)\n{\n   /* The following variable will be set to 1 in the event of\n    * an error and skip any further processing\n    */\n   int exit_error = 0;\n   SSL * ssl_connection = NULL;\n\n   ssl_connection = SSL_new(ssl_context);\n   if (ssl_connection == NULL){\n      MSG(\"Error creating SSL connection\\n\");\n      exit_error = 1;\n   }\n\n   if (exit_error == 0){\n      /* Need to do this if we want to have the option of dealing\n       * with self-signed certs\n       */\n      SSL_set_verify(ssl_connection, SSL_VERIFY_NONE, 0);\n   }\n\n   return exit_error ? NULL : ssl_connection;\n}\n\n/*\n * This function opens a socket to an URL for the SSL connection\n */\nstatic int open_ssl_connection(SSL *ssl_connection, char *url,\n                               char *proxy_url, char *proxy_connect,\n                               int check_cert)\n{\n   /* The following variable will be set to 1 in the event of\n    * an error and skip any further processing\n    */\n   int exit_error = 0;\n   int network_socket = -1;\n\n   network_socket = get_network_connection(proxy_url ? proxy_url : url);\n   if (network_socket<0){\n      MSG(\"Network socket create error\\n\");\n      exit_error = 1;\n   }\n\n   if (exit_error == 0 && proxy_connect != NULL) {\n      /* Connect using proxy */\n      ssize_t St;\n      const char *p = proxy_connect;\n      int writelen = strlen(proxy_connect);\n\n      while (writelen > 0) {\n         St = write(network_socket, p, writelen);\n         if (St < 0) {\n            /* Error */\n            if (errno != EINTR) {\n               MSG(\"Error writing to proxy.\\n\");\n               exit_error = 1;\n               break;\n            }\n         } else {\n            p += St;\n            writelen -= St;\n         }\n      }\n\n      if (exit_error == 0) {\n         const size_t buflen = 200;\n         char buf[buflen];\n         Dstr *reply = dStr_new(\"\");\n\n         while (1) {\n            St = read(network_socket, buf, buflen);\n            if (St > 0) {\n               dStr_append_l(reply, buf, St);\n               if (strstr(reply->str, \"\\r\\n\\r\\n\")) {\n                  /* have whole reply header */\n                  if (reply->len >= 12 && reply->str[9] == '2') {\n                     /* e.g. \"HTTP/1.1 200 Connection established[...]\" */\n                     MSG(\"CONNECT through proxy succeeded.\\n\");\n                  } else {\n                     /* TODO: send reply body to dillo */\n                     exit_error = 1;\n                     MSG(\"CONNECT through proxy failed.\\n\");\n                  }\n                  break;\n               }\n            } else if (St < 0) {\n               if (errno != EINTR) {\n                  exit_error = 1;\n                  MSG(\"Error reading from proxy.\\n\");\n                  break;\n               }\n            }\n         }\n         dStr_free(reply, 1);\n      }\n   }\n\n   if (exit_error == 0){\n      /* Configure SSL to use network file descriptor */\n      if (SSL_set_fd(ssl_connection, network_socket) == 0){\n         MSG(\"Error connecting network socket to SSL\\n\");\n         exit_error = 1;\n     }\n   }\n\n   if (exit_error == 0){\n      /* Configure SSL to use the servername */\n      if(SSL_set_tlsext_host_name(ssl_connection, servername) == 0) {\n         MSG(\"Error setting servername to SSL\\n\");\n         exit_error = 1;\n     }\n   }\n   \n   if (exit_error == 0){\n      /*Actually do SSL connection handshake*/\n      if (SSL_connect(ssl_connection) != 1){\n         MSG(\"SSL_connect failed\\n\");\n         ERR_print_errors_fp(stderr);\n         exit_error = 1;\n      }\n   }\n\n   /*Use handle error function to decide what to do*/\n   if (exit_error == 0){\n      if (check_cert && handle_certificate_problem(ssl_connection) < 0){\n         MSG(\"Certificate verification error\\n\");\n         exit_error = 1;\n      }\n   }\n\n   return exit_error ? -1 : network_socket;\n}\n\n/*\n *  This function performs a Gemini request and\n *  returns the response header and code\n */\nint gemini_send_request(SSL *ssl_connection, char *query,\n                        char *buf, size_t size)\n{\n   int gemini_code = -1;\n\n   fprintf(stderr, \"Gemini Request = %s\\n\", query);\n\n   /* Send query we want */\n   SSL_write(ssl_connection, query, (int)strlen(query));\n\n   /* Add end on line if needed */\n   if(query[strlen(query)-1]!='\\n')\n      SSL_write(ssl_connection, \"\\r\\n\", 2);\n      \n   /* Read header line */\n   if(SSL_read_line(ssl_connection, buf, size) < 3) {\n      /* Header line too short... */\n      return -1;\n   }\n\n   /* Parse Gemini code */\n   gemini_code = (buf[0] - '0') * 10 + (buf[1] - '0');\n\n   /* Clear header end of line */\n   while(*buf) {\n      if(*buf == '\\r' || *buf == '\\n') *buf = '\\0';\n      buf++;\n   }\n\n   return gemini_code;\n}\n\n/*\n *  This function does all of the work with SSL\n */\nstatic void yes_ssl_support(void)\n{\n   /* The following variable will be set to 1 in the event of\n    * an error and skip any further processing\n    */\n   int exit_error = 0;\n   SSL_CTX * ssl_context = NULL;\n   SSL * ssl_connection = NULL;\n\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *gemini_query = NULL,\n        *proxy_url = NULL, *proxy_connect = NULL, *check_cert = NULL;\n   char buf[4096], outbuf[4096*4];\n   int ret = 0;\n   int network_socket = -1;\n\n   int gemini_code = -1;\n\n   MSG(\"{In gemini.filter.dpi}\\n\");\n\n   /* Create context and SSL object */\n   if ((ssl_context = init_ssl()) == NULL){\n      MSG(\"Error creating SSL context\\n\");\n      exit_error = 1;\n   }\n\n   /* Init an SSL connection */\n   if (exit_error == 0){\n      if ((ssl_connection = init_ssl_connection(ssl_context)) == NULL){\n         MSG(\"Error creating SSL connection\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Parse connection data */\n   if (exit_error == 0){\n      /*Get the network address and command to be used*/\n      dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n\n      MSG(\"dpip_tag = %s\\n\", dpip_tag);\n\t       \n      cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n      proxy_url = a_Dpip_get_attr(dpip_tag, \"proxy_url\");\n      proxy_connect =\n                  a_Dpip_get_attr(dpip_tag, \"proxy_connect\");\n      url = a_Dpip_get_attr(dpip_tag, \"url\");\n\n      gemini_query = malloc(strlen(url) + 10);\n\n      if(!strchr(url+strlen(\"gemini://\"), '/')) {\n\tsnprintf(gemini_query, strlen(url) + 10, \"%s/\\r\\n\", url);\n      } else {\n\tsnprintf(gemini_query, strlen(url) + 10, \"%s\\r\\n\", url);\n      }\n\n      if(strstr(gemini_query, \"?q=\")) { // input query delete extra piece \"?q=\" -> \"?\"\n\tchar *repl;\n\n\tfor(repl=gemini_query; *repl != '?'; repl++) {}\n\trepl++;\n\tfor(; *repl; repl++) {\n\t  *repl = *(repl+2);\n\t}\n\t\n      }\n      \n      if (!(check_cert = a_Dpip_get_attr(dpip_tag, \"check_cert\"))) {\n         /* allow older dillo versions use this dpi */\n         check_cert = dStrdup(\"true\");\n      }\n\n      if (!url) {\n\tMSG(\"***Value of url is NULL\"\n                    \" - cannot continue\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Open the SSL connection to a server */\n   if (exit_error == 0){\n      network_socket = open_ssl_connection(ssl_connection, url,\n                                           proxy_url, proxy_connect,\n                                           strcmp(check_cert, \"true\") == 0);\n      if (network_socket<0){\n         MSG(\"Network socket create error\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Perform the Gemini request */\n   if (exit_error == 0) {\n      gemini_code = gemini_send_request(ssl_connection, gemini_query,\n                                        buf, sizeof(buf));\n      if(gemini_code < 0) {\n         MSG(\"Error parsing server reponse\\n\");\n         exit_error = 1;\n      }\n\n      fprintf(stderr, \"HEADER LINE = %s\\n\", buf);\n   }\n\n   /* Read the Gemini response */\n   if (exit_error == 0) {\n      char *d_cmd;\n\n      /*Send dpi command*/\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n      a_Dpip_dsh_write_str(sh, 1, d_cmd);\n      dFree(d_cmd);\n\n      switch(gemini_code) {\n\n      case 10: // search\n\n         snprintf(outbuf, sizeof(outbuf),\n                        \"HTTP/1.1 200 OK\\r\\n\"\n                        \"Content-Type: text/html; charset=UTF-8\\r\\n\\r\\n\");\n\n         a_Dpip_dsh_write(sh, 1, outbuf, strlen(outbuf));\n\n         snprintf(outbuf, sizeof(outbuf),\n                  \"<body><form action=\\\"\\\">\"\n                  \"<label for=\\\"q\\\">Search:</label><br>\"\n                  \"<input type=\\\"text\\\" id=\\\"q\\\" name=\\\"q\\\"><br>\"\n                  \"</form></body>\");\n\n         a_Dpip_dsh_write(sh, 1, outbuf, strlen(outbuf));\n         \n         break;\n      \n      case 31: // redirect\n\n         snprintf(outbuf, sizeof(outbuf),\n                 \"HTTP/1.1 301 Moved Permanently\\r\\n\"\n                 \"Location: %s\\r\\n\\r\\n\", buf + 3);\n\n         a_Dpip_dsh_write(sh, 1, outbuf, strlen(outbuf));\n\n         break;\n\n      case 51: // not found\n\n         snprintf(outbuf, sizeof(outbuf),\n                  \"HTTP/1.1 404 Not Found\\r\\n\"\n                  \"Content-Type: text/html; charset=UTF-8\\r\\n\"\n                  \"Content-Length: %zu\\r\\n\\r\\n%s\", strlen(buf), buf);\n\n         a_Dpip_dsh_write(sh, 1, outbuf, strlen(outbuf));\n      \n         break;\n\n      case 20: // ok\n      default:\n\n         if(!strncmp(buf + 3, \"text/gemini\", strlen(\"text/gemini\"))) {\n\n            /*Send HTTP OK header*/\n            snprintf(outbuf, sizeof(outbuf),\n                     \"HTTP/1.1 200 OK\\r\\n\"\n                     \"Content-Type: text/gemini; charset=UTF-8\\r\\n\\r\\n\");\n\n            a_Dpip_dsh_write(sh, 1, outbuf, strlen(outbuf));\n\n            /*Send remaining data*/\n\n            while ((ret = SSL_read_line(ssl_connection, buf, 4096)) > 0 ){\n               a_Dpip_dsh_write(sh, 1, buf, (size_t)ret);\n            }\n\n         } else {\n\n            /*Send HTTP OK header*/\n            snprintf(outbuf, sizeof(outbuf),\n                     \"HTTP/1.1 200 OK\\r\\n\"\n                     \"Content-Type: %s\\r\\n\\r\\n\", buf + 3);\n\n            a_Dpip_dsh_write(sh, 1, outbuf, strlen(outbuf));\n\n            /*Send remaining data*/\n      \n            while ((ret = SSL_read(ssl_connection, buf, 4096)) > 0 ){\n               a_Dpip_dsh_write(sh, 1, buf, (size_t)ret);\n            }\n\n         }\n\n         break;\n\n      }\n\n   }\n\n   /* Begin cleanup of all resources used */\n   dFree(dpip_tag);\n   dFree(cmd);\n   dFree(url);\n   dFree(gemini_query);\n   dFree(proxy_url);\n   dFree(proxy_connect);\n   dFree(check_cert);\n\n   if (network_socket != -1){\n      dClose(network_socket);\n      network_socket = -1;\n   }\n   if (ssl_connection != NULL){\n      SSL_free(ssl_connection);\n      ssl_connection = NULL;\n   }\n   if (ssl_context != NULL){\n      SSL_CTX_free(ssl_context);\n      ssl_context = NULL;\n   }\n}\n\n/*\n * The following function attempts to open up a connection to the\n * remote server and return the file descriptor number of the\n * socket.  Returns -1 in the event of an error\n */\nstatic int get_network_connection(char * url)\n{\n   struct sockaddr_in address;\n   struct hostent *hp;\n\n   int s;\n   int url_offset = 0;\n   int portnum = 1965;\n   uint_t url_look_up_length = 0;\n   char * url_look_up = NULL;\n\n   MSG(\"{get_network_connection}\\n\");\n\n   /*Determine how much of url we chop off as unneeded*/\n   if (dStrnAsciiCasecmp(url, \"gemini://\", 9) == 0){\n      url_offset = 9;\n   }\n\n   /*Find end of URL*/\n\n   if (strpbrk(url+url_offset, \":/\") != NULL){\n      url_look_up_length = strpbrk(url+url_offset, \":/\") - (url+url_offset);\n      url_look_up = dStrndup(url+url_offset, url_look_up_length);\n\n      /*Check for port number*/\n      if (strchr(url+url_offset, ':') ==\n          (url + url_offset + url_look_up_length)){\n         portnum = strtol(url + url_offset + url_look_up_length + 1, NULL, 10);\n      }\n   } else {\n      url_look_up = url + url_offset;\n   }\n\n   root_url = dStrdup(url_look_up);\n\n   memset(servername, 0, sizeof(servername));\n   strncpy(servername, root_url, sizeof(servername) - 1);\n   \n   hp=gethostbyname(url_look_up);\n\n   /*url_look_uip no longer needed, so free if necessary*/\n   if (url_look_up_length != 0){\n      dFree(url_look_up);\n   }\n\n   if (hp == NULL){\n      MSG(\"gethostbyname() failed\\n\");\n      return -1;\n   }\n\n   memset(&address,0,sizeof(address));\n   memcpy((char *)&address.sin_addr, hp->h_addr, (size_t)hp->h_length);\n   address.sin_family=hp->h_addrtype;\n   address.sin_port= htons((u_short)portnum);\n\n   s = socket(hp->h_addrtype, SOCK_STREAM, 0);\n   if (connect(s, (struct sockaddr *)&address, sizeof(address)) != 0){\n      dClose(s);\n      s = -1;\n      MSG(\"errno: %i\\n\", errno);\n   }\n   return s;\n}\n\n\n/* This function is run only when the certificate cannot\n * be completely trusted.  This will notify the user and\n * allow the user to decide what to do.  It may save the\n * certificate to the user's .dillo directory if it is\n * trusted.\n *\n * TODO: Rearrange this to get rid of redundancy.\n *\n * Return value: -1 on abort, 0 or higher on continue\n */\nstatic int handle_certificate_problem(SSL * ssl_connection)\n{\n   int response_number;\n   int ret = -1;\n   long st;\n   char buf[4096], *d_cmd, *msg;\n   X509 * remote_cert;\n\n   remote_cert = SSL_get_peer_certificate(ssl_connection);\n   if (remote_cert == NULL){\n      /*Inform user that remote system cannot be trusted*/\n      d_cmd = a_Dpip_build_cmd(\n         \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n         \"dialog\",\n         \"Dillo Gemini: No certificate!\",\n         \"The remote system is NOT presenting a certificate.\\n\"\n         \"This site CAN NOT be trusted. Sending data is NOT SAFE.\\n\"\n         \"What do I do?\",\n         \"Continue\", \"Cancel\");\n      a_Dpip_dsh_write_str(sh, 1, d_cmd);\n      dFree(d_cmd);\n\n      /*Read the user's response*/\n      response_number = dialog_get_answer_number();\n\n      /*Abort on anything but \"Continue\"*/\n      if (response_number == 1){\n         ret = 0;\n      }\n\n   } else {\n      /*Figure out if (and why) the remote system can't be trusted*/\n      st = SSL_get_verify_result(ssl_connection);\n      switch (st) {\n      case X509_V_OK:      /*Everything is Kosher*/\n         ret = 0;\n         break;\n      case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:\n         /*Either self signed and untrusted*/\n         /*Extract CN from certificate name information*/\n#if OPENSSL_VERSION_NUMBER >= 0x10100000L\n        /*\n         * 'name' field has been removed in openssl-1.1.0 and newer\n         */\n         strcpy(buf, \"('name' field removed)\");\n#else\n         if ((cn = strstr(remote_cert->name, \"/CN=\")) == NULL) {\n            strcpy(buf, \"(no CN given)\");\n         } else {\n            char *cn_end;\n\n            cn += 4;\n\n            if ((cn_end = strstr(cn, \"/\")) == NULL )\n               cn_end = cn + strlen(cn);\n\n            strncpy(buf, cn, (size_t) (cn_end - cn));\n            buf[cn_end - cn] = '\\0';\n         }\n#endif\n         msg = dStrconcat(\"The remote certificate is self-signed and \"\n                          \"untrusted.\\nFor address: \", buf, NULL);\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s alt3=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Untrusted certificate!\", msg,\n            \"Continue\", \"Cancel\", \"Save Certificate\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n         dFree(msg);\n\n         response_number = dialog_get_answer_number();\n         switch (response_number){\n            case 1:\n               ret = 0;\n               break;\n            case 2:\n               break;\n            case 3:\n               /*Save certificate to a file here and recheck the chain*/\n               /*Potential security problems because we are writing\n                *to the filesystem*/\n               save_certificate_home(remote_cert);\n               ret = 1;\n               break;\n            default:\n               break;\n         }\n         break;\n      case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:\n      case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Missing certificate issuer!\",\n            \"The issuer for the remote certificate cannot be found\\n\"\n            \"The authenticity of the remote certificate cannot be trusted\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n\n      case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:\n      case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:\n      case X509_V_ERR_CERT_SIGNATURE_FAILURE:\n      case X509_V_ERR_CRL_SIGNATURE_FAILURE:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Invalid certificate!\",\n            \"The remote certificate signature could not be read\\n\"\n            \"or is invalid and should not be trusted\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n      case X509_V_ERR_CERT_NOT_YET_VALID:\n      case X509_V_ERR_CRL_NOT_YET_VALID:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Certificate not yet valid!\",\n            \"Part of the remote certificate is not yet valid\\n\"\n            \"Certificates usually have a range of dates over which\\n\"\n            \"they are to be considered valid, and the certificate\\n\"\n            \"presented has a starting validity after today's date\\n\"\n            \"You should be cautious about using this site\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n      case X509_V_ERR_CERT_HAS_EXPIRED:\n      case X509_V_ERR_CRL_HAS_EXPIRED:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Expired certificate!\",\n            \"The remote certificate has expired.  The certificate\\n\"\n            \"wasn't designed to last this long. You should avoid \\n\"\n            \"this site.\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n      case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:\n      case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:\n      case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:\n      case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Certificate error!\",\n            \"There was an error in the certificate presented.\\n\"\n            \"Some of the certificate data was improperly formatted\\n\"\n            \"making it impossible to determine if the certificate\\n\"\n            \"is valid.  You should not trust this certificate.\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n      case X509_V_ERR_INVALID_CA:\n      case X509_V_ERR_INVALID_PURPOSE:\n      case X509_V_ERR_CERT_UNTRUSTED:\n      case X509_V_ERR_CERT_REJECTED:\n      case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Certificate chain error!\",\n            \"One of the certificates in the chain is being used\\n\"\n            \"incorrectly (possibly due to configuration problems\\n\"\n            \"with the remote system.  The connection should not\\n\"\n            \"be trusted\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n      case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:\n      case X509_V_ERR_AKID_SKID_MISMATCH:\n      case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Certificate mismatch!\",\n            \"Some of the information presented by the remote system\\n\"\n            \"does not match other information presented\\n\"\n            \"This may be an attempt to eavesdrop on communications\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n      case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Self signed certificate!\",\n            \"Self signed certificate in certificate chain. The certificate \"\n            \"chain could be built up using the untrusted certificates but the \"\n            \"root could not be found locally.\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n      case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Missing issuer certificate!\",\n            \"Unable to get local issuer certificate. The issuer certificate \"\n            \"of an untrusted certificate cannot be found.\",\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n         response_number = dialog_get_answer_number();\n         if (response_number == 1) {\n            ret = 0;\n         }\n         break;\n      default:             /*Need to add more options later*/\n         snprintf(buf, 80,\n                  \"The remote certificate cannot be verified (code %ld)\", st);\n         d_cmd = a_Dpip_build_cmd(\n            \"cmd=%s title=%s msg=%s alt1=%s alt2=%s\",\n            \"dialog\",\n            \"Dillo Gemini: Unverifiable certificate!\", buf,\n            \"Continue\", \"Cancel\");\n         a_Dpip_dsh_write_str(sh, 1, d_cmd);\n         dFree(d_cmd);\n         response_number = dialog_get_answer_number();\n         /*abort on anything but \"Continue\"*/\n         if (response_number == 1){\n            ret = 0;\n         }\n      }\n      X509_free(remote_cert);\n      remote_cert = 0;\n   }\n\n   return ret;\n}\n\n/*\n * Save certificate with a hashed filename.\n * Return: 0 on success, 1 on failure.\n */\nstatic int save_certificate_home(X509 * cert)\n{\n   char buf[4096];\n\n   FILE * fp = NULL;\n   uint_t i = 0;\n   int ret = 1;\n\n   /*Attempt to create .dillo/certs blindly - check later*/\n   snprintf(buf,4096,\"%s/.\" BINNAME \"/\", dGethomedir());\n   mkdir(buf, 01777);\n   snprintf(buf,4096,\"%s/.\" BINNAME \"/certs/\", dGethomedir());\n   mkdir(buf, 01777);\n\n   do {\n      snprintf(buf, 4096, \"%s/.\" BINNAME \"/certs/%lx.%u\",\n               dGethomedir(), X509_subject_name_hash(cert), i);\n\n      fp=fopen(buf, \"r\");\n      if (fp == NULL){\n         /*File name doesn't exist so we can use it safely*/\n         fp=fopen(buf, \"w\");\n         if (fp == NULL){\n            MSG(\"Unable to open cert save file in home dir\\n\");\n            break;\n         } else {\n            PEM_write_X509(fp, cert);\n            fclose(fp);\n            MSG(\"Wrote certificate\\n\");\n            ret = 0;\n            break;\n         }\n      } else {\n         fclose(fp);\n      }\n      i++;\n      /*Don't loop too many times - just give up*/\n   } while (i < 1024);\n\n   return ret;\n}\n\n\n\n#else\n\n\n/*\n * Call this function to display an error message if SSL support\n * isn't available for some reason\n */\nstatic void no_ssl_support(void)\n{\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *http_query = NULL;\n   char *d_cmd;\n\n   /* Read the dpi command from STDIN */\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n\n   MSG(\"{In gemini.filter.dpi}\\n\");\n   MSG(\"no_ssl_support version\\n\");\n\n   cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n   url = a_Dpip_get_attr(dpip_tag, \"url\");\n   http_query = a_Dpip_get_attr(dpip_tag, \"query\");\n\n   MSG(\"{ cmd: %s}\\n\", cmd);\n   MSG(\"{ url: %s}\\n\", url);\n   MSG(\"{ http_query:\\n%s}\\n\", http_query);\n\n   MSG(\"{ sending dpip cmd...}\\n\");\n\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n   a_Dpip_dsh_write_str(sh, 1, d_cmd);\n   dFree(d_cmd);\n\n   MSG(\"{ dpip cmd sent.}\\n\");\n\n   MSG(\"{ sending HTML...}\\n\");\n\n   a_Dpip_dsh_printf(sh, 1,\n      \"Content-type: text/html\\n\\n\"\n      \"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\\n\"\n      \"<html><head><title>SSL support is disabled</title></head>\\n\"\n      \"<body>\\n\"\n      \"<p>\\n\"\n      \"  The gemini dpi was unable to send\\n\"\n      \"  the following HTTP query:\\n\"\n      \"  <blockquote><pre>%s</pre></blockquote>\\n\"\n      \"  because Dillo's prototype plugin for gemini support\"\n      \"  is disabled.\\n\\n\"\n      \"<p>\\n\"\n      \"  If you want to test this <b>alpha</b> support code,\\n\"\n      \"  just reconfigure with <code>--enable-ssl</code>,\\n\"\n      \"  recompile and reinstall.\\n\\n\"\n      \"  (Beware that this gemini support is very limited now)\\n\\n\"\n      \"  To use gemini and SSL, you must have \\n\"\n      \"  the OpenSSL development libraries installed.  Check your\\n\"\n      \"  O/S distribution provider, or check out\\n\"\n      \"  <a href=\\\"http://www.openssl.org\\\">www.openssl.org</a>.\\n\\n\"\n      \"</p>\\n\\n\"\n      \"</body></html>\\n\",\n      http_query\n   );\n   MSG(\"{ HTML content sent.}\\n\");\n\n   dFree(cmd);\n   dFree(url);\n   dFree(http_query);\n   dFree(dpip_tag);\n\n   MSG(\"{ exiting gemini.dpi}\\n\");\n\n}\n\n#endif\n\n\n/*---------------------------------------------------------------------------*/\n\nint download(char *url, char *output_filename,\n             char *proxy_url, char *proxy_connect, int check_cert) {\n   /* The following variable will be set to 1 in the event of\n    * an error and skip any further processing\n    */\n   int exit_error = 0;\n   SSL_CTX * ssl_context = NULL;\n   SSL * ssl_connection = NULL;\n\n   char buf[4096];\n   int ret = 0;\n   int network_socket = -1;\n\n   int gemini_code = -1;\n   FILE *outfile = NULL;\n\n   /* Open output file */\n   if((outfile = fopen(output_filename, \"w\")) == NULL) {\n      MSG(\"Error opening output file\\n\");\n      exit_error = 1;\n   }\n\n   /* Create context and SSL object */\n   if (exit_error == 0){\n      if ((ssl_context = init_ssl()) == NULL){\n         MSG(\"Error creating SSL context\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Init an SSL connection */\n   if (exit_error == 0){\n      if ((ssl_connection = init_ssl_connection(ssl_context)) == NULL){\n         MSG(\"Error creating SSL connection\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Open the SSL connection to a server */\n   if (exit_error == 0){\n      network_socket = open_ssl_connection(ssl_connection, url,\n                                           proxy_url, proxy_connect,\n                                           check_cert);\n      if (network_socket<0){\n         MSG(\"Network socket create error\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Perform the Gemini request */\n   if (exit_error == 0) {\n      gemini_code = gemini_send_request(ssl_connection, url,\n                                        buf, sizeof(buf));\n      if(gemini_code < 0) {\n         MSG(\"Error parsing server reponse\\n\");\n         exit_error = 1;\n      }\n\n   }\n\n   /* Read the Gemini response */\n   if (exit_error == 0) {\n\n      switch(gemini_code) {\n\n      case 10: // search\n\n         MSG(\"Error downloading file: search code received\\n\");\n         exit_error = 1;\n\n         break;\n      \n      case 31: // redirect\n\n         MSG(\"Error downloading file: redirect code received\\n\");\n         exit_error = 1;\n\n         break;\n\n      case 51: // not found\n\n         MSG(\"Error downloading file: not found\\n\");\n         exit_error = 1;\n      \n         break;\n\n      case 20: // ok\n      default:\n\n         /* Save file data */\n         while ((ret = SSL_read(ssl_connection, buf, 4096)) > 0 ){\n            fwrite(buf, 1, (size_t)ret, outfile);\n         }\n\n         break;\n\n      }\n\n   }\n\n   /* Begin cleanup of all resources used */\n   if (network_socket != -1){\n      dClose(network_socket);\n      network_socket = -1;\n   }\n   if (ssl_connection != NULL){\n      SSL_free(ssl_connection);\n      ssl_connection = NULL;\n   }\n   if (ssl_context != NULL){\n      SSL_CTX_free(ssl_context);\n      ssl_context = NULL;\n   }\n   if(outfile != NULL){\n      fclose(outfile);\n      outfile = NULL;\n   }\n\n   return 0;\n}\n\nint main(int argc, char *argv[])\n{\n   char *dpip_tag;\n   int i;\n   \n   if(argc > 1) {\n      /* Downloader behaviour */\n\n      for(i=1; i<argc; i++) {\n         if(!strncmp(argv[i], \"gemini://\", sizeof(\"gemini://\")-1)) break;\n      }\n\n      if(i+1<argc) {\n         /* Found URL to download */\n\n#ifdef ENABLE_SSL\n         return download(argv[i], argv[i+1], NULL, NULL, 0);\n#else\n         fprintf(stderr, \"Error: compiled without SSL support: \"\n                         \"file download disabled\\n\");\n         return 1;\n#endif\n\n      } else {\n         fprintf(stderr, \"usage: %s gemini_url output_filename\\n\", argv[0]);\n         return 1;\n      }\n      \n   } else {\n      /* Standard DPI behaviour */\n\n      /* Initialize the SockHandler for this filter dpi */\n      sh = a_Dpip_dsh_new(STDIN_FILENO, STDOUT_FILENO, 8*1024);\n\n      /* Authenticate our client... */\n      if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||\n          a_Dpip_check_auth(dpip_tag) < 0) {\n         MSG(\"can't authenticate request: %s\\n\", dStrerror(errno));\n         a_Dpip_dsh_close(sh);\n         return 1;\n      }\n      dFree(dpip_tag);\n\n#ifdef ENABLE_SSL\n      yes_ssl_support();\n#else\n      no_ssl_support();\n#endif\n\n      /* Finish the SockHandler */\n      a_Dpip_dsh_close(sh);\n      a_Dpip_dsh_free(sh);\n\n      dFree(root_url);\n\n      MSG(\"{ exiting gemini.dpi}\\n\");\n\n      return 0;\n   }\n}\n\n"
  },
  {
    "path": "dpi/gopher.c",
    "content": "/*\n * Dpi for Gopher.\n *\n *\n *\n *                            W A R N I N G\n *\n * One of the important things to have in mind is about whether\n * unix domain sockets (UDS) are secure for gopher. I mean, root can always\n * snoop on sockets (regardless of permissions) so he'd be able to \"see\" all\n * the traffic. OTOH, if someone has root access on a machine he can do\n * anything, and that includes modifying the binaries, peeking-up in\n * memory space, installing a key-grabber, ...\n *\n *\n * Copyright 2003, 2004 Jorge Arellano Cid <jcid@dillo.org>\n * Copyright 2004 Garrett Kajmowicz <gkajmowi@tbaytel.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n/*\n * TODO: a lot of things, this is just a bare bones example.\n *\n * For instance:\n * - Certificate authentication (asking the user in case it can't be verified)\n * - Certificate management.\n * - Session caching ...\n *\n */\n\n#include <config.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <sys/un.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <signal.h>\n#include <sys/wait.h>\n#include <errno.h>\n#include <sys/time.h>\n#include <sys/stat.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n\n/*\n * Debugging macros\n */\n#define SILENT 0\n#define _MSG(...)\n#if SILENT\n #define MSG(...)\n#else\n #define MSG(...)  fprintf(stderr, \"[gopher dpi]: \" __VA_ARGS__)\n#endif\n\nchar servername[1024];\n\n/*---------------------------------------------------------------------------*/\n/*\n * Global variables\n */\nstatic char *root_url = NULL;  /*Holds the URL we are connecting to*/\nstatic Dsh *sh;\n\n/*\n * The following function attempts to open up a connection to the\n * remote server and return the file descriptor number of the\n * socket.  Returns -1 in the event of an error\n */\nstatic int get_network_connection(char * url)\n{\n   struct sockaddr_in address;\n   struct hostent *hp;\n\n   int s;\n   int url_offset = 0;\n   int portnum = 70;\n   uint_t url_look_up_length = 0;\n   char * url_look_up = NULL;\n\n   MSG(\"{get_network_connection}\\n\");\n\n   /*Determine how much of url we chop off as unneeded*/\n   if (dStrnAsciiCasecmp(url, \"gopher://\", 9) == 0){\n      url_offset = 9;\n   }\n\n   /*Find end of URL*/\n\n   if (strpbrk(url+url_offset, \":/\") != NULL){\n      url_look_up_length = strpbrk(url+url_offset, \":/\") - (url+url_offset);\n      url_look_up = dStrndup(url+url_offset, url_look_up_length);\n\n      /*Check for port number*/\n      if (strchr(url+url_offset, ':') ==\n          (url + url_offset + url_look_up_length)){\n         portnum = strtol(url + url_offset + url_look_up_length + 1, NULL, 10);\n      }\n   } else {\n      url_look_up = url + url_offset;\n   }\n\n   root_url = dStrdup(url_look_up);\n\n   memset(servername, 0, sizeof(servername));\n   strncpy(servername, root_url, sizeof(servername) - 1);\n   \n   hp=gethostbyname(url_look_up);\n\n   /*url_look_uip no longer needed, so free if necessary*/\n   if (url_look_up_length != 0){\n      dFree(url_look_up);\n   }\n\n   if (hp == NULL){\n      MSG(\"gethostbyname() failed\\n\");\n      return -1;\n   }\n\n   memset(&address,0,sizeof(address));\n   memcpy((char *)&address.sin_addr, hp->h_addr, (size_t)hp->h_length);\n   address.sin_family=hp->h_addrtype;\n   address.sin_port= htons((u_short)portnum);\n\n   s = socket(hp->h_addrtype, SOCK_STREAM, 0);\n   if (connect(s, (struct sockaddr *)&address, sizeof(address)) != 0){\n      dClose(s);\n      s = -1;\n      MSG(\"errno: %i\\n\", errno);\n   }\n   return s;\n}\n\n/*\n * This function parses the Gopher URL format used by dillo\n */\nstatic int gopher_parse_url(char *url, char *type, int *url_proto_len,\n                            char *query, size_t max_query_size)\n{\n   *type = '1';\n   *url_proto_len = strlen(\"gopher://\");\n\n   /* Example of Gopher URL to parse:\n      gopher://1::gopher.club:70/phlogs/ */\n\n   if(strlen(url) + 10 >= max_query_size) {\n      MSG(\"Error: URL too long\\n\");\n      return 0;\n   }\n\n   /* Isolate Gopher query */\n   if(!strchr(url+strlen(\"gopher://\"), '/')) {\n      /* add trailing backslash if missing */\n      snprintf(query, max_query_size, \"%s/\\r\\n\", url);\n   } else {\n      snprintf(query, max_query_size, \"%s\\r\\n\", url);\n   }\n\n   /* Parse Gopher type (if present) */\n   if(strstr(query, \"::\")) {\n      *type = strstr(query, \"::\")[-1];\n      *url_proto_len += 3;\n   }\n\n   /* Parse query input (if present) */\n   if(strstr(query, \"?q=\")) { // convert extra piece to tab \"?q=\" -> \"\\t\"\n      char *repl;\n\n      for(repl=query; *repl != '?'; repl++) {}\n      *repl = '\\t';\n      repl++;\n      for(; *repl; repl++) {\n         *repl = *(repl+2);\n      }\n     \n   }\n   \n   return 1;\n}\n\n/*\n * This function opens a connection to a remote server\n */\nstatic int gopher_open_connection(char *url, int url_proto_len,\n                                  char *proxy_url, char *proxy_connect)\n{\n   /* The following variable will be set to 1 in the event of\n    * an error and skip any further processing\n    */\n   int exit_error = 0;\n   int network_socket = -1;\n   \n   /* Open the socket */\n   network_socket = get_network_connection(proxy_url ? proxy_url : (url + url_proto_len));\n   if (network_socket<0){\n      MSG(\"Network socket create error\\n\");\n      exit_error = 1;\n   }\n   \n   /* Setup the proxy */\n   if (exit_error == 0 && proxy_connect != NULL) {\n      ssize_t St;\n      const char *p = proxy_connect;\n      int writelen = strlen(proxy_connect);\n\n      while (writelen > 0) {\n         St = write(network_socket, p, writelen);\n         if (St < 0) {\n            /* Error */\n            if (errno != EINTR) {\n               MSG(\"Error writing to proxy.\\n\");\n               exit_error = 1;\n               break;\n            }\n         } else {\n            p += St;\n            writelen -= St;\n         }\n      }\n      if (exit_error == 0) {\n         const size_t buflen = 200;\n         char buf[buflen];\n         Dstr *reply = dStr_new(\"\");\n\n         while (1) {\n            St = read(network_socket, buf, buflen);\n            if (St > 0) {\n               dStr_append_l(reply, buf, St);\n               if (strstr(reply->str, \"\\r\\n\\r\\n\")) {\n                  /* have whole reply header */\n                  if (reply->len >= 12 && reply->str[9] == '2') {\n                     /* e.g. \"HTTP/1.1 200 Connection established[...]\" */\n                     MSG(\"CONNECT through proxy succeeded.\\n\");\n                  } else {\n                     /* TODO: send reply body to dillo */\n                     exit_error = 1;\n                     MSG(\"CONNECT through proxy failed.\\n\");\n                  }\n                  break;\n               }\n            } else if (St < 0) {\n               if (errno != EINTR) {\n                  exit_error = 1;\n                  MSG(\"Error reading from proxy.\\n\");\n                  break;\n               }\n            }\n         }\n         dStr_free(reply, 1);\n      }\n   }\n   \n   return exit_error ? -1 : network_socket;\n}\n\nstatic void gopher_main(void)\n{\n   /* The following variable will be set to 1 in the event of\n    * an error and skip any further processing\n    */\n   int exit_error = 0;\n\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL,\n        *proxy_url = NULL, *proxy_connect = NULL;\n   char buf[4096], buf2[4096*4];\n   int ret = 0;\n   int network_socket = -1;\n\n   char gopher_query[2048];\n   char gopher_type = '1';\n   int url_proto_len = 0;\n\n   MSG(\"{In gopher.filter.dpi}\\n\");\n\n   /*Get the network address and command to be used*/\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n\n   MSG(\"dpip_tag = %s\\n\", dpip_tag);\n       \n   cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n   proxy_url = a_Dpip_get_attr(dpip_tag, \"proxy_url\");\n   proxy_connect =\n      a_Dpip_get_attr(dpip_tag, \"proxy_connect\");\n   url = a_Dpip_get_attr(dpip_tag, \"url\");\n   \n   if (!url) {\n      MSG(\"***Value of url is NULL\"\n          \" - cannot continue\\n\");\n      exit_error = 1;\n   }\n\n   /* Parse Gopher URL */\n   if (exit_error == 0){\n      ret = gopher_parse_url(url, &gopher_type, &url_proto_len,\n                             gopher_query, sizeof(gopher_query));\n\n      if (ret == 0){\n         MSG(\"Error parsing URL\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Open Gopher connection */\n   if (exit_error == 0){\n      network_socket = gopher_open_connection(url, url_proto_len,\n                                              proxy_url, proxy_connect);\n\n      if (network_socket<0){\n         MSG(\"Error opening the connection\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Send Gopher query */\n   if (exit_error == 0){\n      char *query_piece;\n\n      fprintf(stderr, \"Gopher Request = %s\\n\", gopher_query);\n\n      query_piece = strchr(gopher_query+url_proto_len, '/');\n      write(network_socket, query_piece, (int)strlen(query_piece));\n   }\n\n   /* Read Gopher response */\n   if (exit_error == 0) {\n      char *d_cmd;\n\n      /*Send dpi command*/\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n      a_Dpip_dsh_write_str(sh, 1, d_cmd);\n      dFree(d_cmd);\n\n      /*Send HTTP OK header*/\n\n      switch (gopher_type) {\n\n      case '0':\n         snprintf(buf2, sizeof(buf2),\n                  \"HTTP/1.1 200 OK\\r\\n\"\n                  \"Content-Type: text/plain; charset=UTF-8\\r\\n\\r\\n\");\n         break;\n\n      case 'g':\n         snprintf(buf2, sizeof(buf2),\n                  \"HTTP/1.1 200 OK\\r\\n\"\n                  \"Content-Type: image/gif\\r\\n\\r\\n\");\n         break;\n\n      case 'p':\n         snprintf(buf2, sizeof(buf2),\n                  \"HTTP/1.1 200 OK\\r\\n\"\n                  \"Content-Type: image/png\\r\\n\\r\\n\");\n         break;\n\n      case 'h':\n         snprintf(buf2, sizeof(buf2),\n                  \"HTTP/1.1 200 OK\\r\\n\"\n                  \"Content-Type: text/html; charset=UTF-8\\r\\n\\r\\n\");\n         break;\n\n      case 'X':\n         snprintf(buf2, sizeof(buf2),\n                  \"HTTP/1.1 200 OK\\r\\n\"\n                  \"Content-Type: text/xml; charset=UTF-8\\r\\n\\r\\n\");\n         break;\n\n      case '1':\n      case '7':\n         snprintf(buf2, sizeof(buf2),\n                  \"HTTP/1.1 200 OK\\r\\n\"\n                  \"Content-Type: text/gopher; charset=UTF-8\\r\\n\\r\\n\");\n         break;\n\n      default:\n         snprintf(buf2, sizeof(buf2),\n                  \"HTTP/1.1 200 OK\\r\\n\"\n                  \"Content-Type: application/octet-stream\\r\\n\\r\\n\");\n      }\n      \n      a_Dpip_dsh_write(sh, 1, buf2, strlen(buf2));\n\n      /*Send data*/\n\n      while ((ret = read(network_socket, buf, 4096)) > 0 ){\n         a_Dpip_dsh_write(sh, 1, buf, (size_t)ret);\n      }\n\n   }\n\n   /*Begin cleanup of all resources used*/\n   dFree(dpip_tag);\n   dFree(cmd);\n   dFree(url);\n   dFree(proxy_url);\n   dFree(proxy_connect);\n\n   if (network_socket != -1){\n      dClose(network_socket);\n      network_socket = -1;\n   }\n\n}\n\n/*---------------------------------------------------------------------------*/\n\nint download(char *url, char *output_filename,\n             char *proxy_url, char *proxy_connect, int check_cert) {\n   /* The following variable will be set to 1 in the event of\n    * an error and skip any further processing\n    */\n   int exit_error = 0;\n\n   char buf[4096];\n   int ret = 0;\n   int network_socket = -1;\n\n   char gopher_query[2048];\n   char gopher_type = '1';\n   int url_proto_len = 0;\n\n   FILE *outfile = NULL;\n\n   /* Open output file */\n   if((outfile = fopen(output_filename, \"w\")) == NULL) {\n      MSG(\"Error opening output file\\n\");\n      exit_error = 1;\n   }\n\n   /* Parse Gopher URL */\n   if (exit_error == 0){\n      ret = gopher_parse_url(url, &gopher_type, &url_proto_len,\n                             gopher_query, sizeof(gopher_query));\n\n      if (ret == 0){\n         MSG(\"Error parsing URL\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Open Gopher connection */\n   if (exit_error == 0){\n      network_socket = gopher_open_connection(url, url_proto_len,\n                                              proxy_url, proxy_connect);\n\n      if (network_socket<0){\n         MSG(\"Error opening the connection\\n\");\n         exit_error = 1;\n      }\n   }\n\n   /* Send Gopher query */\n   if (exit_error == 0){\n      char *query_piece;\n\n      fprintf(stderr, \"Gopher Request = %s\\n\", gopher_query);\n\n      query_piece = strchr(gopher_query+url_proto_len, '/');\n      write(network_socket, query_piece, (int)strlen(query_piece));\n   }\n\n   /* Read Gopher response */\n   if (exit_error == 0) {\n\n      /*Send data*/\n\n      while ((ret = read(network_socket, buf, 4096)) > 0 ){\n         fwrite(buf, 1, (size_t)ret, outfile);\n      }\n\n   }\n\n   /* Begin cleanup of all resources used */\n   if (network_socket != -1){\n      dClose(network_socket);\n      network_socket = -1;\n   }\n   if(outfile != NULL){\n      fclose(outfile);\n      outfile = NULL;\n   }\n\n   return 0;\n}\n\nint main(int argc, char *argv[])\n{\n   char *dpip_tag;\n   int i = 0;\n   \n   if(argc > 1) {\n      /* Downloader behaviour */\n\n      for(i=1; i<argc; i++) {\n         if(!strncmp(argv[i], \"gopher://\", sizeof(\"gopher://\")-1)) break;\n      }\n\n      if(i+1<argc) {\n\n         /* Found URL to download */\n         return download(argv[i], argv[i+1], NULL, NULL, 0);\n\n      } else {\n         fprintf(stderr, \"usage: %s gopher_url output_filename\\n\", argv[0]);\n         return 1;\n      }\n      \n   } else {\n      /* Standard DPI behaviour */\n\n      /* Initialize the SockHandler for this filter dpi */\n      sh = a_Dpip_dsh_new(STDIN_FILENO, STDOUT_FILENO, 8*1024);\n\n      /* Authenticate our client... */\n      if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||\n          a_Dpip_check_auth(dpip_tag) < 0) {\n         MSG(\"can't authenticate request: %s\\n\", dStrerror(errno));\n         a_Dpip_dsh_close(sh);\n         return 1;\n      }\n      dFree(dpip_tag);\n\n      gopher_main();\n\n      /* Finish the SockHandler */\n      a_Dpip_dsh_close(sh);\n      a_Dpip_dsh_free(sh);\n\n      dFree(root_url);\n\n      MSG(\"{ exiting gopher.dpi}\\n\");\n\n      return 0;\n   }\n}\n\n"
  },
  {
    "path": "dpi/hello.c",
    "content": "/*\n * Dpi for \"Hello World\".\n *\n * This server is an example. Play with it and modify to your taste.\n *\n * Copyright 2003-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n#include <unistd.h>\n#include <sys/types.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[hello dpi]: \" __VA_ARGS__)\n\n/*---------------------------------------------------------------------------*/\n\n\n/*\n *\n */\nint main(void)\n{\n   FILE *in_stream;\n   Dsh *sh;\n   char *dpip_tag, *cmd = NULL, *url = NULL, *child_cmd = NULL;\n   char *esc_tag, *d_cmd;\n   size_t n;\n   int ret;\n   char buf[4096];\n   char *choice[] = {\"Window was closed\", \"Yes\", \"No\",\n                      \"Could be\", \"It's OK\", \"Cancel\"};\n                   /* \"Could>be\", \">It's OK\", \"Can'>cel\"};  --for testing */\n   int choice_num = -1;\n\n   MSG(\"starting...\\n\");\n   /* sleep(20) */\n\n   /* Initialize the SockHandler.\n    * This means we'll use stdin for input and stdout for output.\n    * In case of a server dpi, we'd use a socket and pass its file descriptor\n    * twice (e.g. a_Dpip_dsh_new(sock_fd, sock_fd, 1024).\n    * (Note: by now the last parameter is not used) */\n   sh = a_Dpip_dsh_new(STDIN_FILENO, STDOUT_FILENO, 2*1024);\n\n   /* Authenticate our client...\n    * As we're using Internet domain sockets, DPIP checks whether the client\n    * runs with the user's ID, by means of a shared secret. The DPIP API does\n    * the work for us. */\n   if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||\n       a_Dpip_check_auth(dpip_tag) < 0) {\n      MSG(\"can't authenticate request: %s\\n\", dStrerror(errno));\n      a_Dpip_dsh_close(sh);\n      return 1;\n   }\n   dFree(dpip_tag);\n\n   /* Read the dpi command from STDIN\n    * Now we're past the authentication phase, let's see what's dillo\n    * asking from us. a_Dpip_dsh_read_token() will block and return\n    * a full dpip token or null on error (it's commented in dpip.c) */\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   MSG(\"tag = [%s]\\n\", dpip_tag);\n\n   /* Now that we have the dpip_tag, let's isolate the command and url */\n   cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n   url = a_Dpip_get_attr(dpip_tag, \"url\");\n\n/*-- Dialog part */\n/* This is the dialog window. This is an example of interaction with\n * the user. If you're starting to understand dpis, comment this out\n * by switching to \"#if 0\" and the dialog will be disabled. */\n#if 1\n{\n   char *dpip_tag2, *dialog_msg;\n\n   /* Let's confirm the request */\n   /* NOTE: you can send less alternatives (e.g. only alt1 and alt2) */\n   d_cmd = a_Dpip_build_cmd(\n              \"cmd=%s title=%s msg=%s alt1=%s alt2=%s alt3=%s alt4=%s alt5=%s\",\n              \"dialog\", \"Dillo: Hello\", \"Do you want to see the hello page?\",\n              choice[1], choice[2], choice[3], choice[4], choice[5]);\n   a_Dpip_dsh_write_str(sh, 1, d_cmd);\n   dFree(d_cmd);\n\n   /* Get the answer */\n   dpip_tag2 = a_Dpip_dsh_read_token(sh, 1);\n   MSG(\"tag = [%s]\\n\", dpip_tag2);\n\n   /* Get \"msg\" value */\n   dialog_msg = a_Dpip_get_attr(dpip_tag2, \"msg\");\n   choice_num = 0;\n   if (dialog_msg)\n      choice_num = *dialog_msg - '0';\n\n   dFree(dialog_msg);\n   dFree(dpip_tag2);\n}\n#endif\n/*-- EOD part */\n\n   /* Start sending our answer.\n    * (You can read the comments for DPIP API functions in dpip/dpip.c) */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n   a_Dpip_dsh_write_str(sh, 0, d_cmd);\n   dFree(d_cmd);\n\n   a_Dpip_dsh_printf(sh, 0,\n      \"Content-type: text/html\\n\\n\"\n      \"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\\n\"\n      \"<html>\\n\"\n      \"<head><title>Simple dpi test page (hello.dpi)</title></head>\\n\"\n      \"<body><hr><h1>Hello world!</h1><hr>\\n<br><br>\\n\");\n\n   /* Show the choice received with the dialog */\n   a_Dpip_dsh_printf(sh, 0,\n      \"<hr>\\n\"\n      \"<table width='100%%' border='1' bgcolor='burlywood'><tr><td>\\n\"\n      \"<big><em>Dialog question:</em> Do you want to see the hello page?<br>\\n\"\n      \"<em>Answer received:</em> <b>%s</b></big> </table>\\n\"\n      \"<hr>\\n\",\n      choice_num < 0 ? \"There was NO dialog!\" : choice[choice_num]);\n\n   /* Show the dpip tag we received */\n   esc_tag = Escape_html_str(dpip_tag);\n   a_Dpip_dsh_printf(sh, 0,\n      \"<h3>dpip tag received:</h3>\\n\"\n      \"<pre>\\n%s</pre>\\n\"\n      \"<br><small>(<b>dpip:</b> dpi protocol)</small><br><br><br>\\n\",\n      esc_tag);\n   dFree(esc_tag);\n\n\n   /* Now something more interesting,\n    * fork a command and show its feedback.\n    * (An example of generating dynamic content with an external\n    *  program). */\n   if (cmd && url) {\n      child_cmd = dStrdup(\"date -R\");\n      MSG(\"[%s]\\n\", child_cmd);\n\n      /* Fork, exec command, get its output and answer */\n      if ((in_stream = popen(child_cmd, \"r\")) == NULL) {\n         perror(\"popen\");\n         return EXIT_FAILURE;\n      }\n\n      a_Dpip_dsh_write_str(sh, 0, \"<h3>date:</h3>\\n\");\n      a_Dpip_dsh_write_str(sh, 0, \"<pre>\\n\");\n\n      /* Read/Write */\n      while ((n = fread (buf, 1, 4096, in_stream)) > 0) {\n         a_Dpip_dsh_write(sh, 0, buf, n);\n      }\n\n      a_Dpip_dsh_write_str(sh, 0, \"</pre>\\n\");\n\n      if ((ret = pclose(in_stream)) != 0)\n         MSG(\"popen: [%d]\\n\", ret);\n\n      dFree(child_cmd);\n   }\n\n   a_Dpip_dsh_write_str(sh, 1, \"</body></html>\\n\");\n\n   dFree(cmd);\n   dFree(url);\n   dFree(dpip_tag);\n\n   /* Finish the SockHandler */\n   a_Dpip_dsh_close(sh);\n   a_Dpip_dsh_free(sh);\n\n   return 0;\n}\n\n"
  },
  {
    "path": "dpi/man.c",
    "content": "/*\n * File: man.c :)\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Directory scanning is no longer streamed, but it gets sorted instead!\n * Directory entries on top, files next.\n * With new HTML layout.\n */\n\n#include <ctype.h>           /* for isspace */\n#include <errno.h>           /* for errno */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/select.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <dirent.h>\n#include <fcntl.h>\n#include <time.h>\n#include <signal.h>\n#include <netinet/in.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n#include \"d_size.h\"\n#include \"fileutil.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[man dpi]: \" __VA_ARGS__)\n#define _MSG_RAW(...)\n#define MSG_RAW(...)  printf(__VA_ARGS__)\n\n#define HIDE_DOTFILES TRUE\n\n/*\n * Communication flags\n */\n#define MAN_AUTH_OK     1     /* Authentication done */\n#define MAN_READ        2     /* Waiting data */\n#define MAN_WRITE       4     /* Sending data */\n#define MAN_DONE        8     /* Operation done */\n#define MAN_ERR        16     /* Operation error */\n\ntypedef enum {\n   man_arg,\n   man_file\n} ManpageType;\n\ntypedef enum {\n   st_start = 10,\n   st_dpip,\n   st_http,\n   st_pre_content,\n   st_content,\n   st_post_content,\n   st_done,\n   st_err\n} FileState;\n\ntypedef enum {\n   fs_start = 10,\n   fs_in_see_also,\n} FormatState;\n\ntypedef struct {\n   Dsh *sh;\n   char *orig_url;\n   char *manpage;\n   FILE *man;\n   FileState state;\n   FormatState fstate;\n   int err_code;\n   int flags;\n} ClientInfo;\n\n/*\n * Global variables\n */\nstatic int DPIBYE = 0;\n/* A list for the clients we are serving */\nstatic Dlist *Clients;\n/* Set of filedescriptors we're working on */\nfd_set read_set, write_set;\n\n/*\n * Open a pipe to a man process with the specified cmdline arguments\n */\nFILE *Man_open(const char *args, const char *manpage) {\n   int pid1 = 0, pid2 = 0, pid3 = 0;\n   int pipe_1[2];\n   int pipe_2[2];\n   int pipe_3[2];\n   int pipe_4[2];\n\n   if (pipe(pipe_1) < 0) {\n      return NULL;\n   }\n\n   if (pipe(pipe_2) < 0) {\n      close(pipe_1[0]);\n      close(pipe_1[1]);\n      return NULL;\n   }\n\n   if (pipe(pipe_3) < 0) {\n      close(pipe_1[0]);\n      close(pipe_1[1]);\n      close(pipe_2[0]);\n      close(pipe_2[1]);\n      return NULL;\n   }\n\n   if (pipe(pipe_4) < 0) {\n      close(pipe_1[0]);\n      close(pipe_1[1]);\n      close(pipe_2[0]);\n      close(pipe_2[1]);\n      close(pipe_3[0]);\n      close(pipe_3[1]);\n      return NULL;\n   }\n\n   pid1 = fork();\n\n   if (pid1 == -1) {\n      close(pipe_1[0]);\n      close(pipe_1[1]);\n      close(pipe_2[0]);\n      close(pipe_2[1]);\n      close(pipe_3[0]);\n      close(pipe_3[1]);\n      close(pipe_4[0]);\n      close(pipe_4[1]);\n      return NULL;\n   }\n\n   if(pid1 != 0) pid2 = fork();\n\n   if (pid2 == -1) {\n      close(pipe_1[0]);\n      close(pipe_1[1]);\n      close(pipe_2[0]);\n      close(pipe_2[1]);\n      close(pipe_3[0]);\n      close(pipe_3[1]);\n      close(pipe_4[0]);\n      close(pipe_4[1]);\n      return NULL;\n   }\n\n   if(pid1 != 0 && pid2 != 0) pid3 = fork();\n\n   if (pid3 == -1) {\n      close(pipe_1[0]);\n      close(pipe_1[1]);\n      close(pipe_2[0]);\n      close(pipe_2[1]);\n      close(pipe_3[0]);\n      close(pipe_3[1]);\n      close(pipe_4[0]);\n      close(pipe_4[1]);\n      return NULL;\n   }\n\n   if (pid1 == 0) {\n      // child 1\n\n      close(fileno(stdin));\n      close(fileno(stdout));\n      \n      close(pipe_1[1]);\n      close(pipe_2[0]);\n\n      close(pipe_3[1]);\n      close(pipe_3[0]);\n\n      close(pipe_4[1]);\n      close(pipe_4[0]);\n\n      // redirect stdout\n      if (pipe_2[1] != STDOUT_FILENO) {\n\tdup2(pipe_2[1], STDOUT_FILENO);\n\tclose(pipe_2[1]);\n      }\n\n      // redirect stdin\n      if (pipe_1[0] != STDIN_FILENO) {\n\tdup2(pipe_1[0], STDIN_FILENO);\n\tclose(pipe_1[0]);\n      }\n\n      // launch cmd\n      const char* argp[] = {\"man\", args, manpage, NULL};\n      execvp(\"man\", (char**) argp);\n\n      // if the function returns an error has occurred\n      perror(\"execvp\");\n      exit(127);\n   }\n\n   if (pid2 == 0) {\n      // child 2\n\n      close(fileno(stdin));\n      close(fileno(stdout));\n\n      close(pipe_1[1]);\n      close(pipe_1[0]);\n\n      close(pipe_2[1]);\n      close(pipe_3[0]);\n\n      close(pipe_4[1]);\n      close(pipe_4[0]);\n\n      // redirect stdout\n      if (pipe_3[1] != STDOUT_FILENO) {\n\tdup2(pipe_3[1], STDOUT_FILENO);\n\tclose(pipe_3[1]);\n      }\n\n      // redirect stdin\n      if (pipe_2[0] != STDIN_FILENO) {\n\tdup2(pipe_2[0], STDIN_FILENO);\n\tclose(pipe_2[0]);\n      }\n\n      // launch cmd\n      const char* argp[] = {\"col\", \"-b\", NULL};\n      execvp(\"col\", (char**) argp);\n\n      // if the function returns an error has occurred\n      perror(\"execvp\");\n      exit(127);\n   }\n\n   if (pid3 == 0) {\n      // child 3\n\n      close(fileno(stdin));\n      close(fileno(stdout));\n\n      close(pipe_1[1]);\n      close(pipe_1[0]);\n\n      close(pipe_2[1]);\n      close(pipe_2[0]);\n\n      close(pipe_3[1]);\n      close(pipe_4[0]);\n\n      // redirect stdout\n      if (pipe_4[1] != STDOUT_FILENO) {\n\tdup2(pipe_4[1], STDOUT_FILENO);\n\tclose(pipe_4[1]);\n      }\n\n      // redirect stdin\n      if (pipe_3[0] != STDIN_FILENO) {\n\tdup2(pipe_3[0], STDIN_FILENO);\n\tclose(pipe_3[0]);\n      }\n\n      // launch cmd\n      const char* argp[] = {\"sed\", \"s/</\\\\&lt;/g\", NULL};\n      execvp(\"sed\", (char**) argp);\n\n      // if the function returns an error has occurred\n      perror(\"execvp\");\n      exit(127);\n   }\n\n   // parent\n   \n   close(pipe_1[0]);\n   close(pipe_2[1]);\n   close(pipe_2[0]);\n   close(pipe_1[1]); // do not write to child1 stdin\n   close(pipe_3[1]);\n   close(pipe_3[0]);\n   close(pipe_4[1]);\n   \n   return fdopen(pipe_4[0], \"r\");\n}\n\n/*\n * Close an open pipe to man process but handling EINTR\n */\nstatic void Man_close(FILE *fp)\n{\n   if (fp != NULL) fclose(fp);\n}\n\n/*\n * Send an error page\n */\nstatic void Man_prepare_send_error_page(ClientInfo *client, int res,\n                                         const char *orig_url)\n{\n   client->state = st_err;\n   client->err_code = res;\n   client->orig_url = dStrdup(orig_url);\n   client->flags &= ~MAN_READ;\n   client->flags |= MAN_WRITE;\n}\n\n/*\n * Send an error page\n */\nstatic void Man_send_error_page(ClientInfo *client)\n{\n   const char *status;\n   char *d_cmd;\n   Dstr *body = dStr_sized_new(128);\n\n   if (client->err_code == EACCES) {\n      status = \"403 Forbidden\";\n   } else if (client->err_code == ENOENT) {\n      status = \"404 Not Found\";\n   } else {\n      /* good enough */\n      status = \"500 Internal Server Error\";\n   }\n   dStr_append(body, status);\n   dStr_append(body, \"\\n\");\n   dStr_append(body, dStrerror(client->err_code));\n\n   /* Send DPI command */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\",\n                            client->orig_url);\n   a_Dpip_dsh_write_str(client->sh, 0, d_cmd);\n   dFree(d_cmd);\n\n   a_Dpip_dsh_printf(client->sh, 0,\n                     \"HTTP/1.1 %s\\r\\n\"\n                     \"Content-Type: text/plain\\r\\n\"\n                     \"Content-Length: %d\\r\\n\"\n                     \"\\r\\n\"\n                     \"%s\",\n                     status, body->len, body->str);\n   dStr_free(body, TRUE);\n\n   client->flags |= MAN_DONE;\n}\n\n/*\n * Prepare to send HTTP headers and then the file itself.\n */\nstatic int Man_prepare_send_file(ClientInfo *client,\n                                 char *manpage,\n                                 const char *orig_url,\n                                 ManpageType type)\n{\n   FILE *man;\n   int res = -1;\n   char *arg = \"--\";\n   unsigned i, len = strlen(manpage);\n\n   if(type == man_arg) {\n      for(i=0; manpage[i] != '(' && i < len; i++) ;\n      if(manpage[i] == '(') {\n         manpage[i] = '\\0';\n         i++;\n         arg = manpage + i;\n         for(; manpage[i] != ')' && i < len; i++) ;\n         if(manpage[i] == ')') manpage[i] = '\\0';\n      }\n   }\n\n   if (!(man = Man_open(arg, manpage))) {\n      /* prepare an error message */\n      res = errno;\n   } else {\n      /* looks ok, set things accordingly */\n      client->man = man;\n      client->state = st_start;\n      client->fstate = fs_start;\n      client->orig_url = dStrdup(orig_url);\n      client->flags &= ~MAN_READ;\n      client->flags |= MAN_WRITE;\n      res = 0;\n   }\n   return res;\n}\n\n/*\n * Parse path to get manpage filename\n */\nstatic int Man_parse_path(char *path, char **manpage) {\n   char c;\n   int i, len=(int) strlen(path);\n\t\n   struct stat sb;\n\t\n   /* Exclude some dangerous chars in path,\n      to avoid shell commands injection on popen() */\n   for(i=0; i<len; i++) {\n      c=path[i];\n         if(c == '\"' || c == '$' || c == '`' || c == '#' ||\n            c == '<' || c == '>' || c == '|' || c == '\\\\') {\n            MSG(\"error: man_parse_path(): invalid chars in path\\n\");\n            return 0;\n         }\n   }\n\n   /* Check each piece of the path to find the first existing file\n      (it should be the archive file) */\n   for(i=len; i>=0; i--) {\n      if(path[i] != '/' && path[i] != '\\0') continue;\n\n      c = path[i];\n      path[i]='\\0';\n\n      if(!stat(path, &sb)) {\n         if(S_ISDIR(sb.st_mode)) {\n            MSG(\"error: man_parse_path(): directory found instead of file\\n\");\n            return 0;\n         }\n      \n         *manpage = path;\n         MSG(\"manpage=%s\\n\", *manpage);\n         return 1;\n      }\n\n      path[i]=c;\n   }\n\t\n   return 0;\n}\n\n/*\n * Try to stat the file and determine if it's readable.\n */\nstatic void Man_get(ClientInfo *client, char *filename,\n                     const char *orig_url)\n{\n   int res;\n   char *manpage;\n\n   if (!Man_parse_path(filename, &manpage)) {\n      /* is a manpage arg */\n      manpage = filename;\n      client->manpage = dStrdup(manpage);\n\n      /* set up for reading an inner archive file */\n      res = Man_prepare_send_file(client, manpage, orig_url, man_arg);\n   } else {\n      /* is a file path */\n      client->manpage = dStrdup(manpage);\n\n      /* set up for reading an inner archive file */\n      res = Man_prepare_send_file(client, manpage, orig_url, man_file);\n   }\n   if (res != 0) {\n      Man_prepare_send_error_page(client, res, orig_url);\n   }\n}\n\n/*\n * Send HTTP headers and then the file itself.\n */\nstatic int Man_send_file(ClientInfo *client)\n{\n//#define LBUF 1\n#define LBUF 16*1024\n\n   const char *ct = \"text/html\";\n   char buf[LBUF], *d_cmd;\n   int st, st2;\n   char *lr;\n\n   int lstart, i;\n\n   if (client->state == st_start) {\n      /* Send DPI command */\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\",\n                               client->orig_url);\n      a_Dpip_dsh_write_str(client->sh, 1, d_cmd);\n      dFree(d_cmd);\n      client->state = st_dpip;\n\n   } else if (client->state == st_dpip) {\n      /* send HTTP header */\n\n      /* Send HTTP headers */\n      a_Dpip_dsh_write_str(client->sh, 0, \"HTTP/1.1 200 OK\\r\\n\");\n      a_Dpip_dsh_printf(client->sh, 0, \"Content-Type: %s\\r\\n\", ct);\n      /* Todo: determine content length */\n      /*\n      a_Dpip_dsh_printf(client->sh, 1,\n                        \"Content-Length: %ld\\r\\n\"\n                        client->file_sz); */\n      a_Dpip_dsh_printf(client->sh, 1, \"\\r\\n\");\n      client->state = st_http;\n\n   } else if (client->state == st_http) {\n      a_Dpip_dsh_printf(client->sh, 1, \"<title>Man %s</title>\\n\", client->manpage);\n      a_Dpip_dsh_printf(client->sh, 1, \"<pre>\");\n      client->state = st_pre_content;\n   } else if (client->state == st_pre_content) {\n      /* Send body -- raw file contents */\n      if ((st = a_Dpip_dsh_tryflush(client->sh)) < 0) {\n         client->flags |= (st == -3) ? MAN_ERR : 0;\n      } else {\n         /* no pending data, let's send new data */\n         lr = fgets(buf, LBUF, client->man);\n         if (ferror(client->man) != 0) {\n            MSG(\"\\nERROR while reading from file '%s': %s\\n\\n\",\n                client->manpage, dStrerror(errno));\n            client->flags |= MAN_ERR;\n         } else if (lr == NULL) {\n            client->state = st_content;\n         } else {\n            /* ok to write */\n            st2 = strlen(buf);\n\n            /* preprocessing line */\n            if(isupper(buf[0]) && isupper(buf[1])) {\n               a_Dpip_dsh_printf(client->sh, 1, \"<strong>\");\n            }\n            if(buf[0] != ' ' && client->fstate == fs_in_see_also) {\n               client->fstate = fs_start;\n            }\n\n            /* write line content */\n            if(client->fstate == fs_in_see_also) {\n\n               /* skip initial spaces */\n               for(i = 0; buf[i] == ' ' && i < st2; i++) ;\n\n               st = a_Dpip_dsh_trywrite(client->sh, buf, i);\n               client->flags |= (st == -3) ? MAN_ERR : 0;\n\n               lstart = i;\n\n               while(buf[lstart] != '\\n' && lstart < st2) {\n\n                  a_Dpip_dsh_printf(client->sh, 1, \"<a href=\\\"man:\");\n\n                  for(i = lstart; buf[i] != ',' && buf[i] != '\\n' && i < st2; i++) ;\n\n                  st = a_Dpip_dsh_trywrite(client->sh, buf + lstart, i - lstart);\n                  client->flags |= (st == -3) ? MAN_ERR : 0;\n\n                  a_Dpip_dsh_printf(client->sh, 1, \"\\\">\");\n\n                  st = a_Dpip_dsh_trywrite(client->sh, buf + lstart, i - lstart);\n                  client->flags |= (st == -3) ? MAN_ERR : 0;\n\n                  a_Dpip_dsh_printf(client->sh, 1, \"</a>\");\n\n                  lstart = i;\n\n                  for(i = lstart; (buf[i] == ',' || buf[i] == ' ') && i < st2; i++) ;\n                  st = a_Dpip_dsh_trywrite(client->sh, buf + lstart, i - lstart);\n\n                  lstart = i;\n               }\n\n               a_Dpip_dsh_printf(client->sh, 1, \"\\n\");\n\n            } else {\n               st = a_Dpip_dsh_trywrite(client->sh, buf, st2);\n               client->flags |= (st == -3) ? MAN_ERR : 0;\n            }\n\n            /* post processing line */\n            if(isupper(buf[0]) && isupper(buf[1])) {\n               a_Dpip_dsh_printf(client->sh, 1, \"</strong>\");\n            }\n            if(!strncmp(buf, \"SEE ALSO\", 8)) {\n               client->fstate = fs_in_see_also;\n            }\n\n         }\n      }\n   } else if (client->state == st_content) {\n      a_Dpip_dsh_printf(client->sh, 1, \"</pre>\");\n      client->state = st_post_content;\n      client->flags |= MAN_DONE;\n   }\n\n   return 0;\n}\n\n/*\n * Perform any necessary cleanups upon abnormal termination\n */\nstatic void termination_handler(int signum)\n{\n  MSG(\"\\nexit(signum), signum=%d\\n\\n\", signum);\n  exit(signum);\n}\n\n\n/* Client handling ----------------------------------------------------------*/\n\n/*\n * Add a new client to the list.\n */\nstatic ClientInfo *Man_add_client(int sock_fd)\n{\n   ClientInfo *new_client;\n\n   new_client = dNew(ClientInfo, 1);\n   new_client->sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);\n   new_client->orig_url = NULL;\n   new_client->manpage = NULL;\n   new_client->man = NULL;\n   new_client->state = 0;\n   new_client->err_code = 0;\n   new_client->flags = MAN_READ;\n\n   dList_append(Clients, new_client);\n   return new_client;\n}\n\n/*\n * Remove a client from the list.\n */\nstatic void Man_remove_client(ClientInfo *client)\n{\n   dList_remove(Clients, (void *)client);\n\n   _MSG(\"Closing Socket Handler\\n\");\n   a_Dpip_dsh_close(client->sh);\n   a_Dpip_dsh_free(client->sh);\n   Man_close(client->man);\n   dFree(client->orig_url);\n   if (client->manpage) dFree(client->manpage);\n\n   dFree(client);\n}\n\n/*\n * Serve this client.\n */\nstatic void Man_serve_client(void *data, int f_write)\n{\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *path, *resource;\n   ClientInfo *client = data;\n   int st;\n\n   while (1) {\n      _MSG(\"Man_serve_client %p, flags=%d state=%d\\n\",\n          client, client->flags, client->state);\n      if (client->flags & (MAN_DONE | MAN_ERR))\n         break;\n      if (client->flags & MAN_READ) {\n         dpip_tag = a_Dpip_dsh_read_token(client->sh, 0);\n         _MSG(\"dpip_tag={%s}\\n\", dpip_tag);\n         if (!dpip_tag)\n            break;\n      }\n\n      if (client->flags & MAN_READ) {\n         if (!(client->flags & MAN_AUTH_OK)) {\n            /* Authenticate our client... */\n            st = a_Dpip_check_auth(dpip_tag);\n            _MSG(\"a_Dpip_check_auth returned %d\\n\", st);\n            client->flags |= (st == 1) ? MAN_AUTH_OK : MAN_ERR;\n         } else {\n            /* Get file request */\n            cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n            url = a_Dpip_get_attr(dpip_tag, \"url\");\n            path = FileUtil_normalize_path(\"man\", url);\n            resource = FileUtil_get_resource(\"man\", url);\n            if (cmd) {\n               if (strcmp(cmd, \"DpiBye\") == 0) {\n                  DPIBYE = 1;\n                  MSG(\"(pid %d): Got DpiBye.\\n\", (int)getpid());\n                  client->flags |= MAN_DONE;\n               } else if (path) {\n                  Man_get(client, path, url);\n               } else if(resource) {\n                  Man_get(client, resource, url);\n               } else {\n                  client->flags |= MAN_ERR;\n                  MSG(\"ERROR: URL was %s\\n\", url);\n               }\n            }\n            dFree(path);\n            dFree(url);\n            dFree(cmd);\n            dFree(dpip_tag);\n            break;\n         }\n         dFree(dpip_tag);\n\n      } else if (f_write) {\n         /* send our answer */\n         if (client->state == st_err)\n            Man_send_error_page(client);\n         else\n            Man_send_file(client);\n         break;\n      }\n   } /*while*/\n\n   client->flags |= (client->sh->status & DPIP_ERROR) ? MAN_ERR : 0;\n   client->flags |= (client->sh->status & DPIP_EOF) ? MAN_DONE : 0;\n}\n\n/*\n * Serve the client queue.\n */\nstatic void Man_serve_clients()\n{\n   int i, f_read, f_write;\n   ClientInfo *client;\n\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      f_read = FD_ISSET(client->sh->fd_in, &read_set);\n      f_write = FD_ISSET(client->sh->fd_out, &write_set);\n      if (!f_read && !f_write)\n         continue;\n      Man_serve_client(client, f_write);\n      if (client->flags & (MAN_DONE | MAN_ERR)) {\n         Man_remove_client(client);\n         --i;\n      }\n   }\n}\n\n/* --------------------------------------------------------------------------*/\n\n/*\n * Check the fd sets for activity, with a max timeout.\n * return value: 0 if timeout, 1 if input available, -1 if error.\n */\nstatic int Man_check_fds(uint_t seconds)\n{\n   int i, st;\n   ClientInfo *client;\n   struct timeval timeout;\n\n   /* initialize observed file descriptors */\n   FD_ZERO (&read_set);\n   FD_ZERO (&write_set);\n   FD_SET (STDIN_FILENO, &read_set);\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      if (client->flags & MAN_READ)\n         FD_SET (client->sh->fd_in, &read_set);\n      if (client->flags & MAN_WRITE)\n         FD_SET (client->sh->fd_out, &write_set);\n   }\n   _MSG(\"Watching %d fds\\n\", dList_length(Clients) + 1);\n\n   /* Initialize the timeout data structure. */\n   timeout.tv_sec = seconds;\n   timeout.tv_usec = 0;\n\n   do {\n      st = select(FD_SETSIZE, &read_set, &write_set, NULL, &timeout);\n   } while (st == -1 && errno == EINTR);\n/*\n   MSG_RAW(\" (%d%s%s)\", STDIN_FILENO,\n           FD_ISSET(STDIN_FILENO, &read_set) ? \"R\" : \"\",\n           FD_ISSET(STDIN_FILENO, &write_set) ? \"W\" : \"\");\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      MSG_RAW(\" (%d%s%s)\", client->sh->fd_in,\n              FD_ISSET(client->sh->fd_in, &read_set) ? \"R\" : \"\",\n              FD_ISSET(client->sh->fd_out, &write_set) ? \"W\" : \"\");\n   }\n   MSG_RAW(\"\\n\");\n*/\n   return st;\n}\n\n\nint main(void)\n{\n   struct sockaddr_in sin;\n   socklen_t sin_sz;\n   int sock_fd, c_st, st = 1;\n\n   /* Arrange the cleanup function for abnormal terminations */\n   if (signal (SIGINT, termination_handler) == SIG_IGN)\n     signal (SIGINT, SIG_IGN);\n   if (signal (SIGHUP, termination_handler) == SIG_IGN)\n     signal (SIGHUP, SIG_IGN);\n   if (signal (SIGTERM, termination_handler) == SIG_IGN)\n     signal (SIGTERM, SIG_IGN);\n\n   MSG(\"(v.2) accepting connections...\\n\");\n   //sleep(20);\n\n   /* initialize observed file descriptors */\n   FD_ZERO (&read_set);\n   FD_ZERO (&write_set);\n   FD_SET (STDIN_FILENO, &read_set);\n\n   /* Set STDIN socket nonblocking (to ensure accept() never blocks) */\n   fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK | fcntl(STDIN_FILENO, F_GETFL));\n\n   /* initialize Clients list */\n   Clients = dList_new(512);\n\n   /* some OSes may need this... */\n   sin_sz = sizeof(sin);\n\n   /* start the service loop */\n   while (!DPIBYE) {\n      /* wait for activity */\n      do {\n         c_st = Man_check_fds(10);\n      } while (c_st == 0 && !DPIBYE);\n      if (c_st < 0) {\n         MSG(\" select() %s\\n\", dStrerror(errno));\n         break;\n      }\n      if (DPIBYE)\n         break;\n\n      if (FD_ISSET(STDIN_FILENO, &read_set)) {\n         /* accept the incoming connection */\n         do {\n            sock_fd = accept(STDIN_FILENO, (struct sockaddr *)&sin, &sin_sz);\n         } while (sock_fd < 0 && errno == EINTR);\n         if (sock_fd == -1) {\n            if (errno == EAGAIN)\n               continue;\n            MSG(\" accept() %s\\n\", dStrerror(errno));\n            break;\n         } else {\n            _MSG(\" accept() fd=%d\\n\", sock_fd);\n            /* Set nonblocking */\n            fcntl(sock_fd, F_SETFL, O_NONBLOCK | fcntl(sock_fd, F_GETFL));\n            /* Create and initialize a new client */\n            Man_add_client(sock_fd);\n         }\n         continue;\n      }\n\n      Man_serve_clients();\n   }\n\n   if (DPIBYE)\n      st = 0;\n   return st;\n}\n\n"
  },
  {
    "path": "dpi/vsource.c",
    "content": "/*\n * Dpi for \"View source\".\n *\n * This server is an example. Play with it and modify to your taste.\n *\n * Copyright 2010-2015 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n#include <unistd.h>\n#include <sys/types.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <errno.h>\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  fprintf(stderr, \"[vsource dpi]: \" __VA_ARGS__)\n\n/*---------------------------------------------------------------------------*/\n\nconst char *DOCTYPE=\n \"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\";\n\n\nvoid send_dpip_tag(Dsh *sh, char *dpip_tag)\n{\n   a_Dpip_dsh_write_str(sh, 0, \"\\nDpip tag received: \");\n   a_Dpip_dsh_write_str(sh, 0, dpip_tag ? dpip_tag : \"None\");\n   a_Dpip_dsh_write_str(sh, 1, \"\\n\\n\");\n}\n\n/*\n * Send source as plain text\n * (handles embedded null chars correctly).\n */\nvoid send_plain_text(Dsh *sh, int data_size)\n{\n   char *token;\n   int bytes_read = 0, token_size;\n\n   /* Send HTTP header for plain text MIME type */\n   a_Dpip_dsh_write_str(sh, 0, \"Content-type: text/plain\\n\\n\");\n\n   while (bytes_read < data_size &&\n          (token = a_Dpip_dsh_read_token2(sh, 1, &token_size))) {\n      bytes_read += token_size;\n      _MSG(\"data_size=%d bytes_read=%d\\n\", data_size, bytes_read);\n      a_Dpip_dsh_write(sh, 1, token, token_size);\n      dFree(token);\n   }\n}\n\n/*\n * Send source as plain text with line numbers\n * (handles embedded null chars correctly).\n */\nvoid send_numbered_text(Dsh *sh, int data_size)\n{\n   int bytes_read = 0, line = 1, token_size = 0;\n   char *p, *q, *token, line_str[32];\n\n   /* Send HTTP header for plain text MIME type */\n   a_Dpip_dsh_write_str(sh, 0, \"Content-type: text/plain\\n\\n\");\n\n   while (bytes_read < data_size &&\n          (token = a_Dpip_dsh_read_token2(sh, 1, &token_size))) {\n      bytes_read += token_size;\n      p = q = token;\n\n      while (*p) {\n         snprintf(line_str, 32, \"%2d: \", line);\n         a_Dpip_dsh_write_str(sh, 0, line_str);\n         if ((p = strpbrk(q, \"\\r\\n\"))) {\n            a_Dpip_dsh_write(sh, 0, q, p - q + 1);\n            if (*p == '\\r' && p[1] == '\\n')\n               ++p;\n            ++line;\n         } else {\n            /* send all the rest */\n            a_Dpip_dsh_write(sh, 1, q, token_size - (q - token));\n            break;\n         }\n         q = ++p;\n      }\n      dFree(token);\n   }\n}\n\n/*\n * Send source as html text with line numbers\n * (handles embedded null chars correctly).\n */\nvoid send_html_text(Dsh *sh, const char *url, int data_size)\n{\n   int bytes_read = 0, old_line = 0, line = 1, token_size = 0;\n   char *p, *q, *token, line_str[128];\n\n   if (dStrnAsciiCasecmp(url, \"dpi:\", 4) == 0 &&\n       strncmp(url+4, \"/vsource/:\", 10) == 0)\n      url += 14;\n\n   /* Send HTTP header for html text MIME type */\n   a_Dpip_dsh_write_str(sh, 0, \"Content-type: text/html\\n\\n\");\n\n   a_Dpip_dsh_write_str(sh, 0, DOCTYPE);\n   a_Dpip_dsh_printf(sh, 0,\n                     \"\\n\"\n                     \"<html><head>\\n\"\n                     \"<title>Source for %s</title>\\n\"\n                     \"<style type=\\\"text/css\\\">\\n\"\n                     \" body {white-space: pre-wrap; font-family: monospace}\\n\"\n                     \" td.r1 {background-color:#B87333}\\n\"\n                     \" td.r2 {background-color:#DD7F32}\\n\"\n                     \"</style>\\n\"\n                     \"</head>\\n\"\n                     \"<body id=\\\"dillo_vs\\\">\\n<table cellpadding='0'>\\n\", url);\n\n   while (bytes_read < data_size &&\n          (token = a_Dpip_dsh_read_token2(sh, 1, &token_size))) {\n      bytes_read += token_size;\n      p = q = token;\n\n      while (*p) {\n         if (line > old_line) {\n            snprintf(line_str, 128,\n                     \"<tr><td class='%s'>%d%s<td>\",\n                     (line & 1) ? \"r1\" : \"r2\", line,\n                     (line == 1 || (line % 10) == 0) ? \"&nbsp;\" : \"\");\n            a_Dpip_dsh_write_str(sh, 0, line_str);\n            old_line = line;\n         }\n         if ((p = strpbrk(q, \"\\r\\n<&\"))) {\n            if (*p == '\\r' || *p == '\\n') {\n               a_Dpip_dsh_write(sh, 0, q, p - q + 1);\n               if (*p == '\\r' && p[1] == '\\n')\n                  p++;\n               ++line;\n            } else {\n               a_Dpip_dsh_write(sh, 0, q, p - q);\n               a_Dpip_dsh_write_str(sh, 0, (*p == '<') ? \"&lt;\" : \"&amp;\");\n            }\n         } else { \n            /* send all the rest */\n            a_Dpip_dsh_write(sh, 1, q, token_size - (q - token));\n            break;\n         }\n         q = ++p;\n      }\n      dFree(token);\n   }\n\n   a_Dpip_dsh_write_str(sh, 1, \"</table></body></html>\");\n}\n\n/*\n *\n */\nint main(void)\n{\n   Dsh *sh;\n   int data_size;\n   char *dpip_tag, *cmd = NULL, *cmd2 = NULL, *url = NULL, *size_str = NULL;\n   char *d_cmd;\n\n   _MSG(\"starting...\\n\");\n   //sleep(20);\n\n   /* Initialize the SockHandler.\n    * This means we'll use stdin for input and stdout for output.\n    * In case of a server dpi, we'd use a socket and pass its file descriptor\n    * twice (e.g. a_Dpip_dsh_new(sock_fd, sock_fd, 1024).\n    * (Note: by now the last parameter is not used) */\n   sh = a_Dpip_dsh_new(STDIN_FILENO, STDOUT_FILENO, 2*1024);\n\n   /* Authenticate our client...\n    * As we're using Internet domain sockets, DPIP checks whether the client\n    * runs with the user's ID, by means of a shared secret. The DPIP API does\n    * the work for us. */\n   if (!(dpip_tag = a_Dpip_dsh_read_token(sh, 1)) ||\n       a_Dpip_check_auth(dpip_tag) < 0) {\n      MSG(\"can't authenticate request: %s\\n\", dStrerror(errno));\n      a_Dpip_dsh_close(sh);\n      return 1;\n   }\n   dFree(dpip_tag);\n\n   /* Read the dpi command from STDIN\n    * Now we're past the authentication phase, let's see what's dillo\n    * asking from us. a_Dpip_dsh_read_token() will block and return\n    * a full dpip token or null on error (it's commented in dpip.c) */\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   _MSG(\"tag = [%s]\\n\", dpip_tag);\n\n   /* Now that we have the dpip_tag, let's isolate the command and url */\n   cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n   url = a_Dpip_get_attr(dpip_tag, \"url\");\n\n   /* Start sending our answer.\n    * (You can read the comments for DPIP API functions in dpip/dpip.c) */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\", url);\n   a_Dpip_dsh_write_str(sh, 0, d_cmd);\n   dFree(d_cmd);\n   dFree(dpip_tag);\n\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   cmd2 = a_Dpip_get_attr(dpip_tag, \"cmd\");\n   if (cmd2) {\n      if (strcmp(cmd2, \"start_send_page\") == 0 &&\n          (size_str = a_Dpip_get_attr(dpip_tag, \"data_size\"))) {\n         data_size = strtol(size_str, NULL, 10);\n         /* Choose your flavour */\n         //send_plain_text(sh, data_size);\n         //send_numbered_text(sh, data_size);\n         send_html_text(sh, url, data_size);\n      } else if (strcmp(cmd2, \"DpiError\") == 0) {\n         /* Dillo detected an error (other failures just close the socket) */\n         a_Dpip_dsh_write_str(sh, 0, \"Content-type: text/plain\\n\\n\");\n         a_Dpip_dsh_write_str(sh, 1, \"[vsource dpi]: \"\n                                     \"ERROR: Page not cached.\\n\");\n      }\n      dFree(cmd2);\n   }\n\n   dFree(cmd);\n   dFree(url);\n   dFree(size_str);\n   dFree(dpip_tag);\n\n   /* Finish the SockHandler */\n   a_Dpip_dsh_close(sh);\n   a_Dpip_dsh_free(sh);\n\n   return 0;\n}\n\n"
  },
  {
    "path": "dpi/zip.c",
    "content": "/*\n * File: zip.c :)\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Directory scanning is no longer streamed, but it gets sorted instead!\n * Directory entries on top, files next.\n * With new HTML layout.\n */\n\n#include <ctype.h>           /* for isspace */\n#include <errno.h>           /* for errno */\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/select.h>\n#include <sys/socket.h>\n#include <sys/stat.h>\n#include <sys/types.h>\n#include <sys/un.h>\n#include <dirent.h>\n#include <fcntl.h>\n#include <signal.h>\n#include <netinet/in.h>\n\n#define __USE_XOPEN\n#include <time.h>\n\n#include \"../dpip/dpip.h\"\n#include \"dpiutil.h\"\n#include \"d_size.h\"\n#include \"fileutil.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[zip dpi]: \" __VA_ARGS__)\n#define _MSG_RAW(...)\n#define MSG_RAW(...)  printf(__VA_ARGS__)\n\n#define HIDE_DOTFILES TRUE\n\n/*\n * Communication flags\n */\n#define ZIP_AUTH_OK     1     /* Authentication done */\n#define ZIP_READ        2     /* Waiting data */\n#define ZIP_WRITE       4     /* Sending data */\n#define ZIP_DONE        8     /* Operation done */\n#define ZIP_ERR        16     /* Operation error */\n\ntypedef enum {\n   st_start = 10,\n   st_dpip,\n   st_http,\n   st_content,\n   st_done,\n   st_err\n} FileState;\n\ntypedef struct {\n   Dsh *sh;\n   char *orig_url;\n   char *archive_filename;\n   char *inner_filename;\n   FILE *zip;\n   DilloDir *d_dir;\n   FileState state;\n   int err_code;\n   int flags;\n   int old_style;\n} ClientInfo;\n\ntypedef struct {\n   char *date;\n   char *time;\n   char *attr;\n   char *size;\n   char *compr;\n   char *name;\n} ZipFileInfo;\n\n/*\n * Forward references\n */\nstatic const char *Zip_content_type(const char *archive_filename, const char *inner_filename);\n\n/*\n * Global variables\n */\nstatic int DPIBYE = 0;\nstatic int OLD_STYLE = 0;\n/* A list for the clients we are serving */\nstatic Dlist *Clients;\n/* Set of filedescriptors we're working on */\nfd_set read_set, write_set;\n\n/*\n * Open a pipe to an unzip process with the specified cmdline arguments\n */\nFILE *Zip_open(const char *cmd, const char *mode, const char *args,\n             const char *archive_filename, const char *inner_filename) {\n   int pid = 0;\n   int pipe_1[2];\n   int pipe_2[2];\n\n   if (pipe(pipe_1) < 0) {\n      return NULL;\n   }\n\n   if (pipe(pipe_2) < 0) {\n      close(pipe_1[0]);\n      close(pipe_1[1]);\n      return NULL;\n   }\n\n   pid = fork();\n\n   if (pid == -1) {\n      close(pipe_1[0]);\n      close(pipe_1[1]);\n      close(pipe_2[0]);\n      close(pipe_2[1]);\n      return NULL;\n   }\n\n   else if (pid == 0) {\n      // child\n\n      close(fileno(stdin));\n      close(fileno(stdout));\n      \n      close(pipe_1[1]);\n      close(pipe_2[0]);\n\n      // redirect stdout\n      if (pipe_2[1] != STDOUT_FILENO) {\n\tdup2(pipe_2[1], STDOUT_FILENO);\n\tclose(pipe_2[1]);\n      }\n\n      // redirect stdin\n      if (pipe_1[0] != STDIN_FILENO) {\n\tdup2(pipe_1[0], STDIN_FILENO);\n\tclose(pipe_1[0]);\n      }\n\n      // launch cmd\n      const char* argp[] = {cmd, mode, args, archive_filename, inner_filename, NULL};\n      execvp(cmd, (char**) argp);\n\n      // if the function returns an error has occurred\n      perror(\"execvp\");\n      exit(127);\n   }\n\n   // parent\n   \n   close(pipe_1[0]);\n   close(pipe_2[1]);\n   close(pipe_1[1]); // do not write to child stdin\n   \n   return fdopen(pipe_2[0], \"r\");\n}\n\n/*\n * Close an open pipe to zip process but handling EINTR\n */\nstatic void Zip_close(FILE *fp)\n{\n   if (fp != NULL) fclose(fp);\n}\n\n/*\n * Open a zip listing process\n */\nFILE *Zip_open_listing(const char *archive_filename) {\n#if(ZIP_USE_7Z==1)\n   return Zip_open(\"7z\", \"l\", \"-y\", archive_filename, NULL);\n#else\n   return Zip_open(\"unzip\", \"-l\", \"-b\", archive_filename, NULL);\n#endif\n}\n\n/*\n * Open a zip extracting process\n */\nFILE *Zip_open_extract(const char *archive_filename, const char *inner_filename) {\n#if(ZIP_USE_7Z==1)\n   return Zip_open(\"7z\", \"x\", \"-so\", archive_filename,\n                   inner_filename[0] == '/' ? inner_filename + 1 : inner_filename);\n#else\n   return Zip_open(\"unzip\", \"-p\", \"-b\", archive_filename,\n                   inner_filename[0] == '/' ? inner_filename + 1 : inner_filename);\n#endif\n}\n\n/*\n * Parse a zip file listing line\n */\nint Zip_parse_list_line(char *line, ZipFileInfo *zfi) {\n   char *end;\n\n#define ZIP_NEXT_LINE_TOKEN {            \\\n   line = strchr(line, ' ');             \\\n   if(!line) goto ZIP_PARSE_LINE_ERROR;  \\\n   *line='\\0';                           \\\n   line++;                               \\\n   while(*line==' ') line++;             \\\n}\n\n   while(*line==' ') line++;\n\n#if(ZIP_USE_7Z==1)\n   zfi->date = line;\n\n   ZIP_NEXT_LINE_TOKEN;\n   zfi->time = line;\n\n   ZIP_NEXT_LINE_TOKEN;\t\n   zfi->attr = line;\n\n   ZIP_NEXT_LINE_TOKEN;\n   zfi->size = line;\n\n   ZIP_NEXT_LINE_TOKEN;\n   zfi->compr = line;\n\n   ZIP_NEXT_LINE_TOKEN;\n   zfi->name = line;\n#else\n   zfi->size = line;\n\n   ZIP_NEXT_LINE_TOKEN;\n   zfi->date = line;\n\n   ZIP_NEXT_LINE_TOKEN;\t\n   zfi->time = line;\n\n   ZIP_NEXT_LINE_TOKEN;\n   zfi->name = line;\n#endif\n\t\n#undef ZIP_NEXT_LINE_TOKEN\n\n   end = strchr(line, '\\r');\n   if(!end) end = strchr(line, '\\n');\n   if(end) *end='\\0';\n\n   return 1;\n\t\nZIP_PARSE_LINE_ERROR:\n   MSG(\"error: zip_parse_list_line(): \"\n       \"could not parse line: %s\\n\", zfi->date);\n   return 0;\n}\n\n/*\n * Allocate a DilloDir structure, set safe values in it and sort the entries.\n */\nstatic DilloDir *Zip_dillodir_fs_new(const char *archive_filename)\n{\n   struct stat sb;\n   struct tm tm;\n   FILE *zip;\n   DilloDir *Ddir;\n   char *full_path, *timestamp;\n   ZipFileInfo zfi;\n   int in_file_listing;\n   char line[1024];\n\n   if (!(zip = Zip_open_listing(archive_filename)))\n      return NULL;\n\n   Ddir = FileUtil_dillodir_new(archive_filename);\n\n   in_file_listing = 0;\n\n   memset(&tm, 0, sizeof(tm));\n\n   while(fgets(line, sizeof(line) - 1, zip)) {\n      if(!strncmp(line, \"-----\", 5)) {\n         in_file_listing = !in_file_listing;\n         continue;\n      }\n\n      if(in_file_listing) {\n         if(Zip_parse_list_line(line, &zfi)) {\n            /* Add file to archive listing */\n            full_path = dStrconcat(archive_filename, \"/\", zfi.name, NULL);\n\n            sb.st_size = atoi(zfi.size);\n            sb.st_mode = S_IFREG;\n\n            timestamp = dStrconcat(zfi.date, \" \", zfi.time, NULL);\n#if(ZIP_USE_7Z == 1)\n            strptime(timestamp, \"%Y-%m-%d %H:%M:%S\", &tm);\n#else\n            strptime(timestamp, \"%m-%d-%Y %H:%M\", &tm);\n#endif\n            sb.st_mtime = mktime(&tm);\n            free(timestamp);\n\n            FileUtil_dillodir_add(Ddir, full_path, sb);\n         }\n      }\n   }\n\n   Zip_close(zip);\n\n   /* sort the entries */\n   dList_sort(Ddir->flist, (dCompareFunc)FileUtil_comp);\n\n   return Ddir;\n}\n\n/*\n * Based on the extension, return the content_type for the file.\n * (if there's no extension, analyze the data and try to figure it out)\n */\nstatic const char *Zip_content_type(const char *archive_filename, const char *inner_filename)\n{\n   FILE *zip;\n   const char *ct;\n   char buf[256];\n   ssize_t buf_size;\n\n   if (!(ct = FileUtil_ext(inner_filename))) {\n      /* everything failed, let's analyze the data... */\n      zip = Zip_open_extract(archive_filename, inner_filename);\n      if (zip) {\n         if ((buf_size = fread(buf, 1, 256, zip)) > 0) {\n            ct = FileUtil_get_content_type_from_data(buf, (size_t)buf_size);\n         }\n         Zip_close(zip);\n      }\n   }\n   _MSG(\"Zip_content_type: archive_filename=%s inner_filename=%s ct=%s\\n\", archive_filename, inner_filename, ct);\n   return ct;\n}\n\n/*\n * Send the HTML directory page in HTTP.\n */\nstatic void Zip_send_dir(ClientInfo *client)\n{\n   int n;\n   char *d_cmd;\n   const char *filecont;\n   FileInfo *finfo;\n   DilloDir *Ddir = client->d_dir;\n\n   if (client->state == st_start) {\n      /* Send DPI command */\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\",\n                               client->orig_url);\n      a_Dpip_dsh_write_str(client->sh, 1, d_cmd);\n      dFree(d_cmd);\n      client->state = st_dpip;\n\n   } else if (client->state == st_dpip) {\n      /* send HTTP header and HTML top part */\n\n      /* Send page title */\n      FileUtil_print_page_header(client->sh, \"zip\", Ddir->dirname, client->old_style);\n\n      /* Output the parent directory */\n      FileUtil_print_parent_dir(client->sh, \"file\", Ddir->dirname); /* handled with file dpi */\n\n      /* HTML style toggle */\n      a_Dpip_dsh_write_str(client->sh, 0,\n         \"&nbsp;&nbsp;<a href='dpi:/zip/toggle'>%</a>\\n\");\n\t \n      /* Output the file listing table */\n      FileUtil_print_table_header(client->sh, dList_length(Ddir->flist), client->old_style);\n\n      client->state = st_http;\n\n   } else if (client->state == st_http) {\n      /* send directories as HTML contents */\n      for (n = 0; n < dList_length(Ddir->flist); ++n) {\n         finfo = dList_nth_data(Ddir->flist,n);\n         filecont = Zip_content_type(client->archive_filename, finfo->filename);\n         FileUtil_print_info(client->sh, finfo, n+1, filecont, client->old_style);\n      }\n\n      FileUtil_print_table_footer(client->sh, dList_length(Ddir->flist), client->old_style);\n\n      FileUtil_print_page_footer(client->sh, client->old_style);\n\n      client->state = st_content;\n      client->flags |= ZIP_DONE;\n   }\n}\n\n/*\n * Send an error page\n */\nstatic void Zip_prepare_send_error_page(ClientInfo *client, int res,\n                                         const char *orig_url)\n{\n   client->state = st_err;\n   client->err_code = res;\n   client->orig_url = dStrdup(orig_url);\n   client->flags &= ~ZIP_READ;\n   client->flags |= ZIP_WRITE;\n}\n\n/*\n * Send an error page\n */\nstatic void Zip_send_error_page(ClientInfo *client)\n{\n   const char *status;\n   char *d_cmd;\n   Dstr *body = dStr_sized_new(128);\n\n   if (client->err_code == EACCES) {\n      status = \"403 Forbidden\";\n   } else if (client->err_code == ENOENT) {\n      status = \"404 Not Found\";\n   } else {\n      /* good enough */\n      status = \"500 Internal Server Error\";\n   }\n   dStr_append(body, status);\n   dStr_append(body, \"\\n\");\n   dStr_append(body, dStrerror(client->err_code));\n\n   /* Send DPI command */\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\",\n                            client->orig_url);\n   a_Dpip_dsh_write_str(client->sh, 0, d_cmd);\n   dFree(d_cmd);\n\n   a_Dpip_dsh_printf(client->sh, 0,\n                     \"HTTP/1.1 %s\\r\\n\"\n                     \"Content-Type: text/plain\\r\\n\"\n                     \"Content-Length: %d\\r\\n\"\n                     \"\\r\\n\"\n                     \"%s\",\n                     status, body->len, body->str);\n   dStr_free(body, TRUE);\n\n   client->flags |= ZIP_DONE;\n}\n\n/*\n * Scan the directory, sort and prepare to send it enclosed in HTTP.\n */\nstatic int Zip_prepare_send_dir(ClientInfo *client,\n                                 const char *archive_filename, const char *orig_url)\n{\n   Dstr *ds_dirname;\n   DilloDir *Ddir;\n\n   /* Let's make sure this directory url has a trailing slash */\n   ds_dirname = dStr_new(archive_filename);\n   if (ds_dirname->str[ds_dirname->len - 1] != '/')\n      dStr_append(ds_dirname, \"/\");\n\n   /* Let's get a structure ready for transfer */\n   Ddir = Zip_dillodir_fs_new(archive_filename);\n   dStr_free(ds_dirname, TRUE);\n   if (Ddir) {\n      /* looks ok, set things accordingly */\n      client->orig_url = dStrdup(orig_url);\n      client->d_dir = Ddir;\n      client->state = st_start;\n      client->flags &= ~ZIP_READ;\n      client->flags |= ZIP_WRITE;\n      return 0;\n   } else\n      return EACCES;\n}\n\n/*\n * Prepare to send HTTP headers and then the file itself.\n */\nstatic int Zip_prepare_send_file(ClientInfo *client,\n                                  const char *archive_filename,\n                                  const char *inner_filename,\n                                  const char *orig_url)\n{\n   FILE *zip;\n   int res = -1;\n\n   if (!(zip = Zip_open_extract(archive_filename, inner_filename))) {\n      /* prepare an error message */\n      res = errno;\n   } else {\n      /* looks ok, set things accordingly */\n      client->zip = zip;\n      client->d_dir = NULL;\n      client->state = st_start;\n      client->orig_url = dStrdup(orig_url);\n      client->flags &= ~ZIP_READ;\n      client->flags |= ZIP_WRITE;\n      res = 0;\n   }\n   return res;\n}\n\n/*\n * Parse path to get archive filename and inner filename (if any)\n */\nstatic int Zip_parse_path(char *path, char **archive_filename,\n                            char **inner_filename) {\n   char c;\n   int i, len=(int) strlen(path);\n\t\n   struct stat sb;\n\t\n   /* Exclude some dangerous chars in path,\n      to avoid shell commands injection on popen() */\n   for(i=0; i<len; i++) {\n      c=path[i];\n         if(c == '\"' || c == '$' || c == '`' || c == '#' ||\n            c == '<' || c == '>' || c == '|' || c == '\\\\') {\n            MSG(\"error: zip_parse_path(): invalid chars in path\\n\");\n            return 0;\n         }\n   }\n\n   /* Check each piece of the path to find the first existing file\n      (it should be the archive file) */\n   for(i=len; i>=0; i--) {\n      if(path[i] != '/' && path[i] != '\\0') continue;\n\n      c = path[i];\n      path[i]='\\0';\n\n      if(!stat(path, &sb)) {\n         if(S_ISDIR(sb.st_mode)) {\n            MSG(\"error: zip_parse_path(): directory found instead of file\\n\");\n            return 0;\n         }\n      \n         *archive_filename = path;\n         *inner_filename = i<len-1 ? path+i+1 : NULL;\n         MSG(\"archive=%s, inner=%s\\n\", *archive_filename, *inner_filename);\n         return 1;\n      }\n\n      path[i]=c;\n   }\n\t\n   return 0;\n}\n\n/*\n * Try to stat the file and determine if it's readable.\n */\nstatic void Zip_get(ClientInfo *client, char *filename,\n                     const char *orig_url)\n{\n   int res;\n   char *archive_filename, *inner_filename;\n\n   if (!Zip_parse_path(filename, &archive_filename, &inner_filename)) {\n      /* parse and stat failed, prepare a file-not-found error. */\n      res = ENOENT;\n   } else {\n      client->archive_filename = dStrdup(archive_filename);\n      client->inner_filename = dStrdup(inner_filename);\n\n      if (!inner_filename) {\n         /* set up for reading archive listing */\n         res = Zip_prepare_send_dir(client, archive_filename, orig_url);\n      } else {\n         /* set up for reading an inner archive file */\n         res = Zip_prepare_send_file(client, archive_filename,\n                                     inner_filename, orig_url);\n      }\n   }\n   if (res != 0) {\n      Zip_prepare_send_error_page(client, res, orig_url);\n   }\n}\n\n/*\n * Send HTTP headers and then the file itself.\n */\nstatic int Zip_send_file(ClientInfo *client)\n{\n//#define LBUF 1\n#define LBUF 16*1024\n\n   const char *ct;\n   const char *unknown_type = \"application/octet-stream\";\n   char buf[LBUF], *d_cmd, *name;\n   int st, st2, namelen;\n   bool_t gzipped = FALSE;\n\n   if (client->state == st_start) {\n      /* Send DPI command */\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"start_send_page\",\n                               client->orig_url);\n      a_Dpip_dsh_write_str(client->sh, 1, d_cmd);\n      dFree(d_cmd);\n      client->state = st_dpip;\n\n   } else if (client->state == st_dpip) {\n      /* send HTTP header */\n\n      /* Check for gzipped file */\n      namelen = strlen(client->inner_filename);\n      if (namelen > 3 &&\n          !dStrAsciiCasecmp(client->inner_filename + namelen - 3, \".gz\")) {\n         gzipped = TRUE;\n         namelen -= 3;\n      }\n      /* Content-Type info is based on filename extension (with \".gz\" removed).\n       * If there's no known extension, perform data sniffing.\n       * If this doesn't lead to a conclusion, use \"application/octet-stream\".\n       */\n      name = dStrndup(client->inner_filename, namelen);\n      if (!(ct = Zip_content_type(client->archive_filename, name)))\n         ct = unknown_type;\n      dFree(name);\n\n      /* Send HTTP headers */\n      a_Dpip_dsh_write_str(client->sh, 0, \"HTTP/1.1 200 OK\\r\\n\");\n      if (gzipped) {\n         a_Dpip_dsh_write_str(client->sh, 0, \"Content-Encoding: gzip\\r\\n\");\n      }\n      if (!gzipped || strcmp(ct, unknown_type)) {\n         a_Dpip_dsh_printf(client->sh, 0, \"Content-Type: %s\\r\\n\", ct);\n      } else {\n         /* If we don't know type for gzipped data, let dillo figure it out. */\n      }\n      /* Todo: determine content length */\n      /*\n      a_Dpip_dsh_printf(client->sh, 1,\n                        \"Content-Length: %ld\\r\\n\"\n                        client->file_sz); */\n      a_Dpip_dsh_printf(client->sh, 1, \"\\r\\n\");\n      client->state = st_http;\n\n   } else if (client->state == st_http) {\n      /* Send body -- raw file contents */\n      if ((st = a_Dpip_dsh_tryflush(client->sh)) < 0) {\n         client->flags |= (st == -3) ? ZIP_ERR : 0;\n      } else {\n         /* no pending data, let's send new data */\n         st2 = fread(buf, 1, LBUF, client->zip);\n         if (st2 < 0) {\n            MSG(\"\\nERROR while reading from file '%s': %s\\n\\n\",\n                client->inner_filename, dStrerror(errno));\n            client->flags |= ZIP_ERR;\n         } else {\n            /* ok to write */\n            st = a_Dpip_dsh_trywrite(client->sh, buf, st2);\n            client->flags |= (st == -3) ? ZIP_ERR : 0;\n         }\n\t \n\t if (feof(client->zip)) {\n            client->state = st_content;\n            client->flags |= ZIP_DONE;\n         }\n      }\n   }\n\n   return 0;\n}\n\n/*\n * Set the style flag and ask for a reload, so it shows immediately.\n */\nstatic void Zip_toggle_html_style(ClientInfo *client)\n{\n   char *d_cmd;\n\n   OLD_STYLE = !OLD_STYLE;\n   d_cmd = a_Dpip_build_cmd(\"cmd=%s\", \"reload_request\");\n   a_Dpip_dsh_write_str(client->sh, 1, d_cmd);\n   dFree(d_cmd);\n}\n\n/*\n * Perform any necessary cleanups upon abnormal termination\n */\nstatic void termination_handler(int signum)\n{\n  MSG(\"\\nexit(signum), signum=%d\\n\\n\", signum);\n  exit(signum);\n}\n\n\n/* Client handling ----------------------------------------------------------*/\n\n/*\n * Add a new client to the list.\n */\nstatic ClientInfo *Zip_add_client(int sock_fd)\n{\n   ClientInfo *new_client;\n\n   new_client = dNew(ClientInfo, 1);\n   new_client->sh = a_Dpip_dsh_new(sock_fd, sock_fd, 8*1024);\n   new_client->orig_url = NULL;\n   new_client->archive_filename = NULL;\n   new_client->inner_filename = NULL;\n   new_client->zip = NULL;\n   new_client->d_dir = NULL;\n   new_client->state = 0;\n   new_client->err_code = 0;\n   new_client->flags = ZIP_READ;\n   new_client->old_style = OLD_STYLE;\n\n   dList_append(Clients, new_client);\n   return new_client;\n}\n\n/*\n * Remove a client from the list.\n */\nstatic void Zip_remove_client(ClientInfo *client)\n{\n   dList_remove(Clients, (void *)client);\n\n   _MSG(\"Closing Socket Handler\\n\");\n   a_Dpip_dsh_close(client->sh);\n   a_Dpip_dsh_free(client->sh);\n   Zip_close(client->zip);\n   dFree(client->orig_url);\n   if (client->archive_filename) dFree(client->archive_filename);\n   if (client->inner_filename) dFree(client->inner_filename);\n   FileUtil_dillodir_free(client->d_dir);\n\n   dFree(client);\n}\n\n/*\n * Serve this client.\n */\nstatic void Zip_serve_client(void *data, int f_write)\n{\n   char *dpip_tag = NULL, *cmd = NULL, *url = NULL, *path;\n   ClientInfo *client = data;\n   int st;\n\n   while (1) {\n      _MSG(\"Zip_serve_client %p, flags=%d state=%d\\n\",\n          client, client->flags, client->state);\n      if (client->flags & (ZIP_DONE | ZIP_ERR))\n         break;\n      if (client->flags & ZIP_READ) {\n         dpip_tag = a_Dpip_dsh_read_token(client->sh, 0);\n         _MSG(\"dpip_tag={%s}\\n\", dpip_tag);\n         if (!dpip_tag)\n            break;\n      }\n\n      if (client->flags & ZIP_READ) {\n         if (!(client->flags & ZIP_AUTH_OK)) {\n            /* Authenticate our client... */\n            st = a_Dpip_check_auth(dpip_tag);\n            _MSG(\"a_Dpip_check_auth returned %d\\n\", st);\n            client->flags |= (st == 1) ? ZIP_AUTH_OK : ZIP_ERR;\n         } else {\n            /* Get file request */\n            cmd = a_Dpip_get_attr(dpip_tag, \"cmd\");\n            url = a_Dpip_get_attr(dpip_tag, \"url\");\n            path = FileUtil_normalize_path(\"zip\", url);\n            if (cmd) {\n               if (strcmp(cmd, \"DpiBye\") == 0) {\n                  DPIBYE = 1;\n                  MSG(\"(pid %d): Got DpiBye.\\n\", (int)getpid());\n                  client->flags |= ZIP_DONE;\n               } else if (url && dStrnAsciiCasecmp(url, \"dpi:\", 4) == 0 &&\n                          strcmp(url+4, \"/zip/toggle\") == 0) {\n                  Zip_toggle_html_style(client);\n               } else if (path) {\n                  Zip_get(client, path, url);\n               } else {\n                  client->flags |= ZIP_ERR;\n                  MSG(\"ERROR: URL was %s\\n\", url);\n               }\n            }\n            dFree(path);\n            dFree(url);\n            dFree(cmd);\n            dFree(dpip_tag);\n            break;\n         }\n         dFree(dpip_tag);\n\n      } else if (f_write) {\n         /* send our answer */\n         if (client->state == st_err)\n            Zip_send_error_page(client);\n         else if (client->d_dir)\n            Zip_send_dir(client);\n         else\n            Zip_send_file(client);\n         break;\n      }\n   } /*while*/\n\n   client->flags |= (client->sh->status & DPIP_ERROR) ? ZIP_ERR : 0;\n   client->flags |= (client->sh->status & DPIP_EOF) ? ZIP_DONE : 0;\n}\n\n/*\n * Serve the client queue.\n */\nstatic void Zip_serve_clients()\n{\n   int i, f_read, f_write;\n   ClientInfo *client;\n\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      f_read = FD_ISSET(client->sh->fd_in, &read_set);\n      f_write = FD_ISSET(client->sh->fd_out, &write_set);\n      if (!f_read && !f_write)\n         continue;\n      Zip_serve_client(client, f_write);\n      if (client->flags & (ZIP_DONE | ZIP_ERR)) {\n         Zip_remove_client(client);\n         --i;\n      }\n   }\n}\n\n/* --------------------------------------------------------------------------*/\n\n/*\n * Check the fd sets for activity, with a max timeout.\n * return value: 0 if timeout, 1 if input available, -1 if error.\n */\nstatic int Zip_check_fds(uint_t seconds)\n{\n   int i, st;\n   ClientInfo *client;\n   struct timeval timeout;\n\n   /* initialize observed file descriptors */\n   FD_ZERO (&read_set);\n   FD_ZERO (&write_set);\n   FD_SET (STDIN_FILENO, &read_set);\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      if (client->flags & ZIP_READ)\n         FD_SET (client->sh->fd_in, &read_set);\n      if (client->flags & ZIP_WRITE)\n         FD_SET (client->sh->fd_out, &write_set);\n   }\n   _MSG(\"Watching %d fds\\n\", dList_length(Clients) + 1);\n\n   /* Initialize the timeout data structure. */\n   timeout.tv_sec = seconds;\n   timeout.tv_usec = 0;\n\n   do {\n      st = select(FD_SETSIZE, &read_set, &write_set, NULL, &timeout);\n   } while (st == -1 && errno == EINTR);\n/*\n   MSG_RAW(\" (%d%s%s)\", STDIN_FILENO,\n           FD_ISSET(STDIN_FILENO, &read_set) ? \"R\" : \"\",\n           FD_ISSET(STDIN_FILENO, &write_set) ? \"W\" : \"\");\n   for (i = 0; (client = dList_nth_data(Clients, i)); ++i) {\n      MSG_RAW(\" (%d%s%s)\", client->sh->fd_in,\n              FD_ISSET(client->sh->fd_in, &read_set) ? \"R\" : \"\",\n              FD_ISSET(client->sh->fd_out, &write_set) ? \"W\" : \"\");\n   }\n   MSG_RAW(\"\\n\");\n*/\n   return st;\n}\n\n\nint main(void)\n{\n   struct sockaddr_in sin;\n   socklen_t sin_sz;\n   int sock_fd, c_st, st = 1;\n\n   /* Arrange the cleanup function for abnormal terminations */\n   if (signal (SIGINT, termination_handler) == SIG_IGN)\n     signal (SIGINT, SIG_IGN);\n   if (signal (SIGHUP, termination_handler) == SIG_IGN)\n     signal (SIGHUP, SIG_IGN);\n   if (signal (SIGTERM, termination_handler) == SIG_IGN)\n     signal (SIGTERM, SIG_IGN);\n\n   MSG(\"(v.2) accepting connections...\\n\");\n   //sleep(20);\n\n   /* initialize observed file descriptors */\n   FD_ZERO (&read_set);\n   FD_ZERO (&write_set);\n   FD_SET (STDIN_FILENO, &read_set);\n\n   /* Set STDIN socket nonblocking (to ensure accept() never blocks) */\n   fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK | fcntl(STDIN_FILENO, F_GETFL));\n\n   /* initialize Clients list */\n   Clients = dList_new(512);\n\n   /* some OSes may need this... */\n   sin_sz = sizeof(sin);\n\n   /* start the service loop */\n   while (!DPIBYE) {\n      /* wait for activity */\n      do {\n         c_st = Zip_check_fds(10);\n      } while (c_st == 0 && !DPIBYE);\n      if (c_st < 0) {\n         MSG(\" select() %s\\n\", dStrerror(errno));\n         break;\n      }\n      if (DPIBYE)\n         break;\n\n      if (FD_ISSET(STDIN_FILENO, &read_set)) {\n         /* accept the incoming connection */\n         do {\n            sock_fd = accept(STDIN_FILENO, (struct sockaddr *)&sin, &sin_sz);\n         } while (sock_fd < 0 && errno == EINTR);\n         if (sock_fd == -1) {\n            if (errno == EAGAIN)\n               continue;\n            MSG(\" accept() %s\\n\", dStrerror(errno));\n            break;\n         } else {\n            _MSG(\" accept() fd=%d\\n\", sock_fd);\n            /* Set nonblocking */\n            fcntl(sock_fd, F_SETFL, O_NONBLOCK | fcntl(sock_fd, F_GETFL));\n            /* Create and initialize a new client */\n            Zip_add_client(sock_fd);\n         }\n         continue;\n      }\n\n      Zip_serve_clients();\n   }\n\n   if (DPIBYE)\n      st = 0;\n   return st;\n}\n\n"
  },
  {
    "path": "dpid/Makefile",
    "content": "include ../Makefile.options\n\nCFLAGS_EXTRA = -DDPIDRC_SYS='\"$(DPIDRC_SYS)\"' -DBINNAME='\"$(BINNAME)\"'\n\nall: dpid-plus dpidc-plus dpidrc\n\ndpid-plus: dpi.o dpi_socket_dir.o  dpid-plus.o dpid_common.o main.o  misc_new.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(LINK) dpi.o dpi_socket_dir.o  dpid-plus.o dpid_common.o main.o  misc_new.o ../dpip/libDpip.a ../dlib/libDlib.a\n\ndpidc-plus: dpidc-plus.o ../dpip/libDpip.a  ../dlib/libDlib.a \n\t$(LINK) dpidc-plus.o ../dpip/libDpip.a  ../dlib/libDlib.a\n\ndpidrc: dpidrc.in\n\tsed -e \"s|[@]libdir[@]|$(LIBDIR)|\" -e \"s|[@]binname[@]|$(BINNAME)|\" ./dpidrc.in > dpidrc\n\ndpi.o: dpi.h dpi.c\n\t$(COMPILE) $(CFLAGS_EXTRA) -c dpi.c\n\ndpi_socket_dir.o: dpi_socket_dir.h dpi_socket_dir.c\n\t$(COMPILE) $(CFLAGS_EXTRA) -c dpi_socket_dir.c\n\ndpid-plus.o: dpid-plus.h dpid-plus.c\n\t$(COMPILE) $(CFLAGS_EXTRA) -c dpid-plus.c\n\ndpid_common.o: dpid_common.h dpid_common.c\n\t$(COMPILE) $(CFLAGS_EXTRA) -c dpid_common.c\n\nmain.o: main.c\n\t$(COMPILE) $(CFLAGS_EXTRA) -c main.c\n\nmisc_new.o: misc_new.h misc_new.c\n\t$(COMPILE) $(CFLAGS_EXTRA) -c misc_new.c\n\ndpidc-plus.o: dpidc-plus.c\n\t$(COMPILE) $(CFLAGS_EXTRA) -c dpidc-plus.c\n\nclean:\n\trm -f *.o dpid-plus dpidc-plus dpidrc\n\ninstall: all\n\t$(INSTALL_SH) -c -d \"$(DILLO_ETCDIR)\"\n\t$(INSTALL) -c -m 644 dpidrc \"$(DPIDRC_SYS)\"\n\t$(INSTALL_SH) -c -d \"$(DILLO_BINDIR)\"\n\t$(INSTALL) -c dpid-plus \"$(DILLO_BINDIR)\"\n\t$(INSTALL) -c dpidc-plus \"$(DILLO_BINDIR)\"\n\nuninstall:\n\trm -f \"$(DPIDRC_SYS)\"\n\trm -f \"$(DILLO_BINDIR)/dpid-plus\"\n\trm -f \"$(DILLO_BINDIR)/dpidc-plus\"\n"
  },
  {
    "path": "dpid/TODO",
    "content": "Todo List\n\n File dpi_service.c\n This module should be removed because its original functions\n have been removed or modified. Put these functions in dpid.c\n\n File dpi_service.h\n This module should be removed because its original functions\n have been removed or modified. Put these functions in dpid.c\n\n Add other file types, but first we need to add files associated\n with a dpi to the design.\n\n Global SRS_NAME\n Should read this from dillorc\n\n File dpid_common.h\n The dpid error codes will be used in the next patch\n\n Global main()\n + add new plugins when they become available\n __________________________________________________________\n\nRemove global variables.\n\nUse a singly linked list for dpi_attr_list\n\nAllow dpis to be registered one at a time\n\nOnly run dpis listed in users dillorc\n\nMAYBE Have dpid accept all connections to dpis (fixes inf loop if dpi crashes before accept)\n"
  },
  {
    "path": "dpid/dpi.c",
    "content": "/*\n   Copyright (C) 2003  Ferdi Franceschini <ferdif@optusnet.com.au>\n\n   This program is free software; you can redistribute it and/or modify\n   it under the terms of the GNU General Public License as published by\n   the Free Software Foundation; either version 3 of the License, or\n   (at your option) any later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*! \\file\n * Access functions for  ~/.dillo/dpi_socket_dir.\n * The most useful function for dillo is a_Dpi_srs, it returns\n * the full path to the dpid service request socket.\n */\n\n#include <errno.h>\n#include <stdlib.h>  /* for exit */\n#include \"dpid_common.h\"\n#include \"dpi.h\"\n#include \"misc_new.h\"\n\n/*! \\Return\n * Returns path to the dpi_socket_dir file\n * Use dFree to free memory\n */\nchar *a_Dpi_sockdir_file(void)\n{\n   char *dpi_socket_dir, *dirfile_path = \"/.\" BINNAME \"/dpi_socket_dir\";\n\n   dpi_socket_dir = dStrconcat(dGethomedir(), dirfile_path, NULL);\n   return dpi_socket_dir;\n}\n\n/*! Read socket directory path from ~/.dillo/dpi_socket_dir\n * \\Return\n * socket directory path or NULL if the dpi_socket_dir file does not exist.\n * \\Note\n * This function exits if ~/.dillo does not exist or\n * if the dpi_socket_dir file cannot be opened for a\n * reason other than it does not exist.\n */\n\nchar *a_Dpi_rd_dpi_socket_dir(char *dirname)\n{\n   FILE *dir;\n   char *sockdir = NULL, *rcpath;\n\n   rcpath = dStrconcat(dGethomedir(), \"/.\" BINNAME, NULL);\n\n   /* If .dillo does not exist it is an unrecoverable error */\n   if (access(rcpath, F_OK) == -1) {\n      ERRMSG(\"a_Dpi_rd_dpi_socket_dir\", \"access\", errno);\n      MSG_ERR(\" - %s\\n\", rcpath);\n      exit(1);\n   }\n   dFree(rcpath);\n\n   if ((dir = fopen(dirname, \"r\")) != NULL) {\n      sockdir = dGetline(dir);\n      fclose(dir);\n   } else if (errno == ENOENT) {\n      ERRMSG(\"a_Dpi_rd_dpi_socket_dir\", \"fopen\", errno);\n      MSG_ERR(\" - %s\\n\", dirname);\n   } else if (errno != ENOENT) {\n      ERRMSG(\"a_Dpi_rd_dpi_socket_dir\", \"fopen\", errno);\n      MSG_ERR(\" - %s\\n\", dirname);\n      exit(1);\n   }\n\n   return sockdir;\n}\n\n/*!\n * \\Modifies\n * srs_name\n * \\Return\n * The service request socket name with its complete path.\n */\nchar *a_Dpi_srs(void)\n{\n   char *dirfile_path, *sockdir, *srs_name;\n\n   dirfile_path = a_Dpi_sockdir_file();\n   sockdir = dStrstrip(a_Dpi_rd_dpi_socket_dir(dirfile_path));\n   srs_name = dStrconcat(sockdir, \"/\", \"dpid-plus.srs\", NULL);\n   dFree(sockdir);\n   dFree(dirfile_path);\n   return (srs_name);\n}\n"
  },
  {
    "path": "dpid/dpi.h",
    "content": "/*! \\file\n * Access functions for  ~/.dillo/dpi_socket_dir.\n * The most useful function for dillo is a_Dpi_srs, it returns\n * the full path to the dpid service request socket.\n */\n\n#ifndef DPI_H\n#define DPI_H\n\n#include <unistd.h>      /* for socklen_t */\n#include <sys/socket.h>  /* for socklen_t and AF_LOCAL */\n\n/* Some systems may not have this one... */\n#ifndef AF_LOCAL\n   #define AF_LOCAL AF_UNIX\n#endif\n\n/* This one is tricky, some sources state it should include the byte\n * for the terminating NULL, and others say it shouldn't.\n * The other way is to only use this one when a native SUN_LEN is not present,\n * but as dillo has used this for a long time successfully, here it goes.\n */\n# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \\\n                        + strlen ((ptr)->sun_path))\n\n/*!\n * dpi commands\n */\nenum {\n   UNKNOWN_CMD,\n   AUTH_CMD, /* authentication */\n   BYE_CMD,  /* \"DpiBye\" */\n   CHECK_SERVER_CMD, /* \"check_server\" */\n   REGISTER_ALL_CMD, /* \"register_all\" */\n   REGISTER_SERVICE_CMD /* \"register_service\" */\n};\n\n\nchar *a_Dpi_sockdir_file(void);\n\nchar *a_Dpi_rd_dpi_socket_dir(char *dirname);\n\nchar *a_Dpi_srs(void);\n\n#endif\n"
  },
  {
    "path": "dpid/dpi_socket_dir.c",
    "content": "/*\n   Copyright (C) 2003  Ferdi Franceschini <ferdif@optusnet.com.au>\n\n   This program is free software; you can redistribute it and/or modify\n   it under the terms of the GNU General Public License as published by\n   the Free Software Foundation; either version 3 of the License, or\n   (at your option) any later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*! \\file\n * Create a per user temporary directory for dpi sockets\n */\n\n#include <errno.h>\n#include <stdlib.h>\n#include \"dpid_common.h\"\n#include \"dpi.h\"\n#include \"misc_new.h\"\n#include \"dpi_socket_dir.h\" /* for function prototypes */\n\n/*! Save socket directory name in ~/.dillo/dpi_socket_dir\n * \\Return\n * \\li 1 on success\n * \\li -1 on failure\n */\nint w_dpi_socket_dir(char *dirname, char *sockdir)\n{\n   FILE *dir;\n\n   if ((dir = fopen(dirname, \"w\")) == NULL) {\n      ERRMSG(\"w_dpi_socket_dir\", \"fopen\", errno);\n      return (-1);\n   }\n   fprintf(dir, \"%s\", sockdir);\n   fclose(dir);\n   return (1);\n}\n\n/*! Test that socket directory exists and is a directory\n * \\Return\n * \\li 1 on success\n * \\li 0 on failure\n * \\bug Does not check permissions or that it's a symbolic link\n * to another directory.\n */\nint tst_dir(char *dir)\n{\n   char *dirtest;\n   int ret = 0;\n\n   /* test for a directory */\n   dirtest = dStrconcat(dir, \"/\", NULL);\n   if (access(dirtest, F_OK) == -1) {\n      ERRMSG(\"tst_dir\", \"access\", errno);\n      MSG_ERR(\" - %s\\n\", dirtest);\n   } else {\n      ret = 1;\n   }\n   dFree(dirtest);\n\n   return ret;\n}\n\n/*! Create socket directory\n * \\Return\n * \\li Socket directory path on success\n * \\li NULL on failure\n */\nchar *mk_sockdir(void)\n{\n   char *template, *logname;\n\n   logname = getenv(\"LOGNAME\") ? getenv(\"LOGNAME\") : BINNAME;\n   template = dStrconcat(\"/tmp/\", logname, \"-\", \"XXXXXX\", NULL);\n   if (a_Misc_mkdtemp(template) == NULL) {\n      ERRMSG(\"mk_sockdir\", \"a_Misc_mkdtemp\", 0);\n      MSG_ERR(\" - %s\\n\", template);\n      dFree(template);\n      return (NULL);\n   }\n   return template;\n}\n\n/*! Create socket directory if it does not exist and save its name in\n * ~/.dillo/dpi_socket_dir.\n * \\Return\n * \\li Socket directory name on success\n * \\li NULL on failure.\n */\nchar *init_sockdir(char *dpi_socket_dir)\n{\n   char *sockdir = NULL;\n   int dir_ok = 0;\n\n   if ((sockdir = a_Dpi_rd_dpi_socket_dir(dpi_socket_dir)) == NULL) {\n      MSG_ERR(\"init_sockdir: The dpi_socket_dir file %s does not exist\\n\",\n              dpi_socket_dir);\n   } else {\n      if ((dir_ok = tst_dir(sockdir)) == 1) {\n         MSG_ERR(\"init_sockdir: The socket directory %s exists and is OK\\n\",\n                 sockdir);\n      } else {\n         MSG_ERR(\"init_sockdir: The socket directory %s does not exist \"\n                 \"or is not a directory\\n\", sockdir);\n         dFree(sockdir);\n      }\n   }\n   if (!dir_ok) {\n      sockdir = mk_sockdir();\n      if (sockdir == NULL) {\n         ERRMSG(\"init_sockdir\", \"mk_sockdir\", 0);\n         MSG_ERR(\" - Failed to create dpi socket directory\\n\");\n      } else if ((w_dpi_socket_dir(dpi_socket_dir, sockdir)) == -1) {\n         ERRMSG(\"init_sockdir\", \"w_dpi_socket_dir\", 0);\n         MSG_ERR(\" - failed to save %s\\n\", sockdir);\n         dFree(sockdir);\n         sockdir = NULL;\n      }\n   }\n   return (sockdir);\n}\n"
  },
  {
    "path": "dpid/dpi_socket_dir.h",
    "content": "/*! \\file\n * Create a per user temporary directory for dpi sockets\n */\n\n#ifndef DPI_SOCKET_DIR_H\n#define DPI_SOCKET_DIR_H\n\n\nint w_dpi_socket_dir(char *dirname, char *sockdir);\n\nint tst_dir(char *dir);\n\nchar *mk_sockdir(void);\n\nchar *init_sockdir(char *dpi_socket_dir);\n\n#endif\n"
  },
  {
    "path": "dpid/dpid-plus.c",
    "content": "/*\n   Copyright (C) 2003  Ferdi Franceschini <ferdif@optusnet.com.au>\n\n   This program is free software; you can redistribute it and/or modify\n   it under the terms of the GNU General Public License as published by\n   the Free Software Foundation; either version 3 of the License, or\n   (at your option) any later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*! \\file\n * Main functions to set-up dpi information and to initialise sockets\n */\n#include <errno.h>\n#include <stdlib.h>             /* for exit */\n#include <fcntl.h>              /* for F_SETFD, F_GETFD, FD_CLOEXEC */\n\n#include <sys/stat.h>\n#include <sys/wait.h>\n#include <sys/socket.h>\n#include <netinet/tcp.h>\n\n#include <unistd.h>\n#include \"dpid_common.h\"\n#include \"dpid-plus.h\"\n#include \"dpi.h\"\n#include \"dpi_socket_dir.h\"\n#include \"misc_new.h\"\n\n#include \"../dpip/dpip.h\"\n\n#define QUEUE 5\n\nvolatile sig_atomic_t caught_sigchld = 0;\nchar *SharedKey = NULL;\n\n/*! Remove dpid_comm_keys file.\n * This avoids that dillo instances connect to a stale port after dpid\n * has exited (e.g. after a reboot).\n */\nvoid cleanup()\n{\n   char *fname;\n   fname = dStrconcat(dGethomedir(), \"/\", dotDILLO_DPID_COMM_KEYS, NULL);\n   unlink(fname);\n   dFree(fname);\n}\n\n/*! Free memory used to describe\n * a set of dpi attributes\n */\nvoid free_dpi_attr(struct dp *dpi_attr)\n{\n   if (dpi_attr->id != NULL) {\n      dFree(dpi_attr->id);\n      dpi_attr->id = NULL;\n   }\n   if (dpi_attr->path != NULL) {\n      dFree(dpi_attr->path);\n      dpi_attr->path = NULL;\n   }\n}\n\n/*! Free memory used by the plugin list\n */\nvoid free_plugin_list(struct dp **dpi_attr_list_ptr, int numdpis)\n{\n   int i;\n   struct dp *dpi_attr_list = *dpi_attr_list_ptr;\n\n   if (dpi_attr_list == NULL)\n      return;\n\n   for (i = 0; i < numdpis; i++)\n      free_dpi_attr(dpi_attr_list + i);\n\n   dFree(dpi_attr_list);\n   *dpi_attr_list_ptr = NULL;\n}\n\n/*! Free memory used by the services list\n */\nvoid free_services_list(Dlist *s_list)\n{\n   int i = 0;\n   struct service *s;\n\n   for (i=0; i < dList_length(s_list) ; i++) {\n      s = dList_nth_data(s_list, i);\n      dFree(s->name);\n   }\n   dList_free(s_list);\n}\n\n/*! Signal handler for SIGINT, SIGQUIT, and SIGTERM. Calls cleanup\n */\nstatic void terminator(int sig)\n{\n   (void) sig; /* suppress unused parameter warning */\n   cleanup();\n   _exit(0);\n}\n\n/*! Establish handler for termination signals\n * and register cleanup with atexit */\nvoid est_dpi_terminator()\n{\n   struct sigaction act;\n   sigset_t block;\n\n   sigemptyset(&block);\n   sigaddset(&block, SIGHUP);\n   sigaddset(&block, SIGINT);\n   sigaddset(&block, SIGQUIT);\n   sigaddset(&block, SIGTERM);\n\n   act.sa_handler = terminator;\n   act.sa_mask = block;\n   act.sa_flags = 0;\n\n   if (sigaction(SIGHUP, &act, NULL) ||\n       sigaction(SIGINT, &act, NULL) ||\n       sigaction(SIGQUIT, &act, NULL) ||\n       sigaction(SIGTERM, &act, NULL)) {\n      ERRMSG(\"est_dpi_terminator\", \"sigaction\", errno);\n      exit(1);\n   }\n\n   if (atexit(cleanup) != 0) {\n      ERRMSG(\"est_dpi_terminator\", \"atexit\", 0);\n      MSG_ERR(\"Hey! atexit failed, how did that happen?\\n\");\n      exit(1);\n   }\n}\n\n/*! Identify a given file\n * Currently there is only one file type associated with dpis.\n * More file types will be added as needed\n */\nenum file_type get_file_type(char *file_name)\n{\n   char *dot = strrchr(file_name, '.');\n\n   if (dot && !strcmp(dot, \".dpi\"))\n      return DPI_FILE;             /* Any filename ending in \".dpi\" */\n   else {\n      MSG_ERR(\"get_file_type: Unknown file type for %s\\n\", file_name);\n      return UNKNOWN_FILE;\n   }\n}\n\n/*! Get dpi directory path from dpidrc\n * \\Return\n * dpi directory on success, NULL on failure\n * \\Important\n * The dpi_dir definition in dpidrc must have no leading white space.\n */\nchar *get_dpi_dir(char *dpidrc)\n{\n   FILE *In;\n   int len;\n   char *rcline = NULL, *value = NULL, *p;\n\n   if ((In = fopen(dpidrc, \"r\")) == NULL) {\n      ERRMSG(\"dpi_dir\", \"fopen\", errno);\n      MSG_ERR(\" - %s\\n\", dpidrc);\n      return (NULL);\n   }\n\n   while ((rcline = dGetline(In)) != NULL) {\n      if (strncmp(rcline, \"dpi_dir\", 7) == 0)\n         break;\n      dFree(rcline);\n   }\n   fclose(In);\n\n   if (!rcline) {\n      ERRMSG(\"dpi_dir\", \"Failed to find a dpi_dir entry in dpidrc\", 0);\n      MSG_ERR(\"Put your dillo plugins path in %s\\n\", dpidrc);\n      MSG_ERR(\"e.g. dpi_dir=/usr/local/lib/\" BINNAME \"/dpi\\n\");\n      MSG_ERR(\"with no leading spaces.\\n\");\n      value = NULL;\n   } else {\n      len = (int) strlen(rcline);\n      if (len && rcline[len - 1] == '\\n')\n         rcline[len - 1] = 0;\n\n      if ((p = strchr(rcline, '='))) {\n         while (*++p == ' ');\n         value = dStrdup(p);\n      } else {\n         ERRMSG(\"dpi_dir\", \"strchr\", 0);\n         MSG_ERR(\" - '=' not found in %s\\n\", rcline);\n         value = NULL;\n      }\n   }\n\n   dFree(rcline);\n   return (value);\n}\n\n/*! Scans a service directory in dpi_dir and fills dpi_attr\n * \\Note\n * Caller must allocate memory for dpi_attr.\n * \\Return\n * \\li 0 on success\n * \\li -1 on failure\n * \\todo\n * Add other file types, but first we need to add files associated with a dpi\n * to the design.\n */\nint get_dpi_attr(char *dpi_dir, char *service, struct dp *dpi_attr)\n{\n   char *service_dir = NULL;\n   struct stat statinfo;\n   enum file_type ftype;\n   int ret = -1;\n   DIR *dir_stream;\n   struct dirent *dir_entry = NULL;\n\n   service_dir = dStrconcat(dpi_dir, \"/\", service, NULL);\n   if (stat(service_dir, &statinfo) == -1) {\n      ERRMSG(\"get_dpi_attr\", \"stat\", errno);\n      MSG_ERR(\"file=%s\\n\", service_dir);\n   } else if ((dir_stream = opendir(service_dir)) == NULL) {\n      ERRMSG(\"get_dpi_attr\", \"opendir\", errno);\n   } else {\n      /* Scan the directory looking for dpi files.\n       * (currently there's only the dpi program, but in the future\n       *  there may also be helper scripts.) */\n      while ( (dir_entry = readdir(dir_stream)) != NULL) {\n         if (dir_entry->d_name[0] == '.')\n            continue;\n\n         ftype = get_file_type(dir_entry->d_name);\n         switch (ftype) {\n            case DPI_FILE:\n               dpi_attr->path =\n                  dStrconcat(service_dir, \"/\", dir_entry->d_name, NULL);\n               dpi_attr->id = dStrdup(service);\n               dpi_attr->port = 0;\n               dpi_attr->pid = 1;\n               if (strstr(dpi_attr->path, \".filter\") != NULL)\n                  dpi_attr->filter = 1;\n               else\n                  dpi_attr->filter = 0;\n               ret = 0;\n               break;\n            default:\n               break;\n         }\n      }\n      closedir(dir_stream);\n\n      if (ret != 0)\n         MSG_ERR(\"get_dpi_attr: No dpi plug-in in %s/%s\\n\",\n                 dpi_dir, service);\n   }\n   dFree(service_dir);\n   return ret;\n}\n\n/*! Register a service\n * Retrieves attributes for \"service\" and stores them\n * in dpi_attr. It looks for \"service\" in ~/.dillo/dpi\n * first, and then in the system wide dpi directory.\n * Caller must allocate memory for dpi_attr.\n * \\Return\n * \\li 0 on success\n * \\li -1 on failure\n */\nint register_service(struct dp *dpi_attr, char *service)\n{\n   char *user_dpi_dir, *dpidrc, *user_service_dir, *dir = NULL;\n   int ret = -1;\n\n   user_dpi_dir = dStrconcat(dGethomedir(), \"/\", dotDILLO_DPI, NULL);\n   user_service_dir =\n       dStrconcat(dGethomedir(), \"/\", dotDILLO_DPI, \"/\", service, NULL);\n\n   dpidrc = dStrconcat(dGethomedir(), \"/\", dotDILLO_DPIDRC, NULL);\n   if (access(dpidrc, F_OK) == -1) {\n      if (access(DPIDRC_SYS, F_OK) == -1) {\n         ERRMSG(\"register_service\", \"Error \", 0);\n         MSG_ERR(\"\\n - There is no %s or %s file\\n\", dpidrc,\n               DPIDRC_SYS);\n         dFree(user_dpi_dir);\n         dFree(user_service_dir);\n         dFree(dpidrc);\n         return(-1);\n      }\n      dFree(dpidrc);\n      dpidrc = dStrdup(DPIDRC_SYS);\n   }\n\n   /* Check home dir for dpis */\n   if (access(user_service_dir, F_OK) == 0) {\n      get_dpi_attr(user_dpi_dir, service, dpi_attr);\n      ret = 0;\n   } else {                     /* Check system wide dpis */\n      if ((dir = get_dpi_dir(dpidrc)) != NULL) {\n         if (access(dir, F_OK) == 0) {\n            get_dpi_attr(dir, service, dpi_attr);\n            ret = 0;\n         } else {\n            ERRMSG(\"register_service\", \"get_dpi_attr failed\", 0);\n         }\n      } else {\n         ERRMSG(\"register_service\", \"dpi_dir: Error getting dpi dir.\", 0);\n      }\n   }\n   dFree(user_dpi_dir);\n   dFree(user_service_dir);\n   dFree(dpidrc);\n   dFree(dir);\n   return ret;\n}\n\n/*!\n * Create dpi directory for available\n * plugins and create plugin list.\n * \\Return\n * \\li Returns number of available plugins on success\n * \\li -1 on failure\n */\nint register_all(struct dp **attlist)\n{\n   DIR *user_dir_stream, *sys_dir_stream;\n   char *user_dpidir = NULL, *sys_dpidir = NULL, *dpidrc = NULL;\n   struct dirent *user_dirent, *sys_dirent;\n   int st;\n   int snum;\n   size_t dp_sz = sizeof(struct dp);\n\n   if (*attlist != NULL) {\n      ERRMSG(\"register_all\", \"attlist parameter should be NULL\", 0);\n      return -1;\n   }\n\n   user_dpidir = dStrconcat(dGethomedir(), \"/\", dotDILLO_DPI, NULL);\n   if (access(user_dpidir, F_OK) == -1) {\n      /* no dpis in user's space */\n      dFree(user_dpidir);\n      user_dpidir = NULL;\n   }\n   dpidrc = dStrconcat(dGethomedir(), \"/\", dotDILLO_DPIDRC, NULL);\n   if (access(dpidrc, F_OK) == -1) {\n      dFree(dpidrc);\n      dpidrc = dStrdup(DPIDRC_SYS);\n      if (access(dpidrc, F_OK) == -1) {\n         dFree(dpidrc);\n         dpidrc = NULL;\n      }\n   }\n   if (!dpidrc || (sys_dpidir = get_dpi_dir(dpidrc)) == NULL)\n      sys_dpidir = NULL;\n   dFree(dpidrc);\n\n   if (!user_dpidir && !sys_dpidir) {\n      ERRMSG(\"register_all\", \"Fatal error \", 0);\n      MSG_ERR(\"\\n - Can't find the directory for dpis.\\n\");\n      exit(1);\n   }\n\n   /* Get list of services in user's .dillo/dpi directory */\n   snum = 0;\n   if (user_dpidir && (user_dir_stream = opendir(user_dpidir)) != NULL) {\n      while ((user_dirent = readdir(user_dir_stream)) != NULL) {\n         if (user_dirent->d_name[0] == '.')\n            continue;\n         *attlist = (struct dp *) dRealloc(*attlist, (snum + 1) * dp_sz);\n         st=get_dpi_attr(user_dpidir, user_dirent->d_name, &(*attlist)[snum]);\n         if (st == 0)\n            snum++;\n      }\n      closedir(user_dir_stream);\n   }\n   if (sys_dpidir && (sys_dir_stream = opendir(sys_dpidir)) != NULL) {\n      /* if system service is not in user list then add it */\n      while ((sys_dirent = readdir(sys_dir_stream)) != NULL) {\n         if (sys_dirent->d_name[0] == '.')\n           continue;\n         *attlist = (struct dp *) dRealloc(*attlist, (snum + 1) * dp_sz);\n         st=get_dpi_attr(sys_dpidir, sys_dirent->d_name, &(*attlist)[snum]);\n         if (st == 0)\n            snum++;\n      }\n      closedir(sys_dir_stream);\n   }\n\n   dFree(sys_dpidir);\n   dFree(user_dpidir);\n\n   /* TODO: do we consider snum == 0 an error?\n    *       (if so, we should return -1 )       */\n   return (snum);\n}\n\n/*\n * Compare two struct service pointers\n * This function is used for sorting services\n */\nstatic int services_alpha_comp(const struct service *s1,\n                               const struct service *s2)\n{\n   return -strcmp(s1->name, s2->name);\n}\n\n/*! Add services reading a dpidrc file\n * each non empty or commented line has the form\n * service = path_relative_to_dpidir\n * \\Return:\n * \\li Returns number of available services on success\n * \\li -1 on failure\n */\nint fill_services_list(struct dp *attlist, int numdpis, Dlist **services_list)\n{\n   FILE *dpidrc_stream;\n   char *p, *line = NULL, *service, *path;\n   int i, st;\n   struct service *s;\n   char *user_dpidir = NULL, *sys_dpidir = NULL, *dpidrc = NULL;\n\n   user_dpidir = dStrconcat(dGethomedir(), \"/\", dotDILLO_DPI, NULL);\n   if (access(user_dpidir, F_OK) == -1) {\n      /* no dpis in user's space */\n      dFree(user_dpidir);\n      user_dpidir = NULL;\n   }\n   dpidrc = dStrconcat(dGethomedir(), \"/\", dotDILLO_DPIDRC, NULL);\n   if (access(dpidrc, F_OK) == -1) {\n      dFree(dpidrc);\n      dpidrc = dStrdup(DPIDRC_SYS);\n      if (access(dpidrc, F_OK) == -1) {\n         dFree(dpidrc);\n         dpidrc = NULL;\n      }\n   }\n   if (!dpidrc || (sys_dpidir = get_dpi_dir(dpidrc)) == NULL)\n      sys_dpidir = NULL;\n\n   if (!user_dpidir && !sys_dpidir) {\n      ERRMSG(\"fill_services_list\", \"Fatal error \", 0);\n      MSG_ERR(\"\\n - Can't find the directory for dpis.\\n\");\n      exit(1);\n   }\n\n   if ((dpidrc_stream = fopen(dpidrc, \"r\")) == NULL) {\n      ERRMSG(\"fill_services_list\", \"popen failed\", errno);\n      dFree(dpidrc);\n      dFree(sys_dpidir);\n      dFree(user_dpidir);\n      return (-1);\n   }\n\n   if (*services_list != NULL) {\n      ERRMSG(\"fill_services_list\", \"services_list parameter is not NULL\", 0);\n      fclose(dpidrc_stream);\n      return -1;\n   }\n   *services_list = dList_new(8);\n\n   /* dpidrc parser loop */\n   for (;(line = dGetline(dpidrc_stream)) != NULL; dFree(line)) {\n      st = dParser_parse_rc_line(&line, &service, &path);\n      if (st < 0) {\n         MSG_ERR(\"dpid: Syntax error in %s: service=\\\"%s\\\" path=\\\"%s\\\"\\n\",\n                 dpidrc, service, path);\n         continue;\n      } else if (st != 0) {\n         continue;\n      }\n\n      _MSG(\"dpid: service=%s, path=%s\\n\", service, path);\n\n      /* ignore dpi_dir silently */\n      if (strcmp(service, \"dpi_dir\") == 0)\n         continue;\n\n      s = dNew(struct service, 1);\n      /* init services list entry */\n      s->name = dStrdup(service);\n      s->dp_index = -1;\n\n      dList_append(*services_list, s);\n      /* search the dpi for a service by its path */\n      for (i = 0; i < numdpis; i++)\n          if ((p = strstr(attlist[i].path, path)) && *(p - 1) == '/' &&\n              strlen(p) == strlen(path))\n             break;\n      /* if the dpi exist bind service and dpi */\n      if (i < numdpis)\n         s->dp_index = i;\n   }\n   fclose(dpidrc_stream);\n\n   dList_sort(*services_list, (dCompareFunc)services_alpha_comp);\n\n   dFree(dpidrc);\n   dFree(sys_dpidir);\n   dFree(user_dpidir);\n\n   return (dList_length(*services_list));\n}\n\n/*\n * Return a socket file descriptor\n * (useful to set socket options in a uniform way)\n */\nstatic int make_socket_fd()\n{\n   int ret, one = 1;\n\n   if ((ret = socket(AF_INET, SOCK_STREAM, 0)) == -1) {\n      ERRMSG(\"make_socket_fd\", \"socket\", errno);\n   } else {\n      /* avoid delays when sending small pieces of data */\n      setsockopt(ret, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));\n   }\n\n   /* set some buffering to increase the transfer's speed */\n   //setsockopt(sock_fd, SOL_SOCKET, SO_SNDBUF,\n   //           &sock_buflen, (socklen_t)sizeof(sock_buflen));\n\n   return ret;\n}\n\n/*! Bind a socket port on localhost. Try to be close to base_port.\n * \\Return\n * \\li listening socket file descriptor on success\n * \\li -1 on failure\n */\nint bind_socket_fd(int base_port, int *p_port)\n{\n   int sock_fd, port;\n   struct sockaddr_in sin;\n   int ok = 0, last_port = base_port + 50;\n\n   if ((sock_fd = make_socket_fd()) == -1) {\n      return (-1);              /* avoids nested ifs */\n   }\n   /* Set the socket FD to close on exec */\n   fcntl(sock_fd, F_SETFD, FD_CLOEXEC | fcntl(sock_fd, F_GETFD));\n\n\n   memset(&sin, 0, sizeof(sin));\n   sin.sin_family = AF_INET;\n   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n\n   /* Try to bind a port on localhost */\n   for (port = base_port; port <= last_port; ++port) {\n      sin.sin_port = htons(port);\n      if ((bind(sock_fd, (struct sockaddr *)&sin, sizeof(sin))) == -1) {\n         if (errno == EADDRINUSE || errno == EADDRNOTAVAIL)\n            continue;\n         ERRMSG(\"bind_socket_fd\", \"bind\", errno);\n      } else if (listen(sock_fd, QUEUE) == -1) {\n         ERRMSG(\"bind_socket_fd\", \"listen\", errno);\n      } else {\n         *p_port = port;\n         ok = 1;\n         break;\n      }\n   }\n   if (port > last_port) {\n      MSG_ERR(\"Hey! Can't find an available port from %d to %d\\n\",\n              base_port, last_port);\n   }\n\n   return ok ? sock_fd : -1;\n}\n\n/*! Save the current port and a shared secret in a file so dillo can find it.\n * \\Return:\n * \\li -1 on failure\n */\nint save_comm_keys(int srs_port)\n{\n   int fd, ret = -1;\n   char *fname, port_str[32];\n\n   fname = dStrconcat(dGethomedir(), \"/\", dotDILLO_DPID_COMM_KEYS, NULL);\n   fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);\n   dFree(fname);\n   if (fd == -1) {\n      MSG(\"save_comm_keys: open %s\\n\", dStrerror(errno));\n   } else {\n      snprintf(port_str, 16, \"%d %s\\n\", srs_port, SharedKey);\n      if (CKD_WRITE(fd, port_str) != -1 && CKD_CLOSE(fd) != -1) {\n         ret = 1;\n      }\n   }\n\n   return ret;\n}\n\n/*! Initialise the service request socket (IDS)\n * \\Return:\n * \\li Number of sockets (1 == success)\n * \\li -1 on failure\n */\nint init_ids_srs_socket()\n{\n   int srs_port, ret = -1;\n\n   FD_ZERO(&sock_set);\n\n   if ((srs_fd = bind_socket_fd(DPID_BASE_PORT, &srs_port)) != -1) {\n      /* create the shared secret */\n      SharedKey = a_Misc_mksecret(8);\n      /* save port number and SharedKey */\n      if (save_comm_keys(srs_port) != -1) {\n         FD_SET(srs_fd, &sock_set);\n         ret = 1;\n      }\n   }\n\n   return ret;\n}\n\n/*! Initialize a single dpi socket\n * \\Return\n * \\li 1 on success\n * \\li -1 on failure\n */\nint init_dpi_socket(struct dp *dpi_attr)\n{\n   int s_fd, port, ret = -1;\n\n   if ((s_fd = bind_socket_fd(DPID_BASE_PORT, &port)) != -1) {\n      dpi_attr->sock_fd = s_fd;\n      dpi_attr->port = port;\n      FD_SET(s_fd, &sock_set);\n      ret = 1;\n   }\n\n   return ret;\n}\n\n/*! Setup sockets for the plugins and add them to\n * the set of sockets (sock_set) watched by select.\n * \\Return\n * \\li Number of sockets on success\n * \\li -1 on failure\n * \\Modifies\n * dpi_attr_list.sa, dpi_attr_list.socket, numsocks, sock_set, srs\n * \\Uses\n * numdpis, srs, srs_name\n */\nint init_all_dpi_sockets(struct dp *dpi_attr_list)\n{\n   int i;\n\n   /* Initialise sockets for each dpi */\n   for (i = 0; i < numdpis; i++) {\n      if (init_dpi_socket(dpi_attr_list + i) == -1)\n         return (-1);\n      numsocks++;\n   }\n\n   return (numsocks);\n}\n\n/*! SIGCHLD handler\n */\nvoid dpi_sigchld(int sig)\n{\n   if (sig == SIGCHLD)\n      caught_sigchld = 1;\n}\n\n/*! Called by main loop when caught_sigchld == 1 */\nvoid handle_sigchld(void)\n{\n   // pid_t pid;\n   int i, status; //, num_active;\n\n   /* For all of the dpis in the current list\n    *    add the ones that have exited to the set of sockets being\n    *    watched by 'select'.\n    */\n   for (i = 0; i < numdpis; i++) {\n      if (waitpid(dpi_attr_list[i].pid, &status, WNOHANG) > 0) {\n         dpi_attr_list[i].pid = 1;\n         FD_SET(dpi_attr_list[i].sock_fd, &sock_set);\n         numsocks++;\n      }\n   }\n\n   /* Wait for any old dpis that have exited */\n   while (waitpid(-1, &status, WNOHANG) > 0)\n      ;\n}\n\n/*! Establish SIGCHLD handler */\nvoid est_dpi_sigchld(void)\n{\n   struct sigaction sigact;\n   sigset_t set;\n\n   (void) sigemptyset(&set);\n   sigact.sa_handler = dpi_sigchld;\n   sigact.sa_mask = set;\n   sigact.sa_flags = SA_NOCLDSTOP;\n   if (sigaction(SIGCHLD, &sigact, NULL) == -1) {\n      ERRMSG(\"est_dpi_sigchld\", \"sigaction\", errno);\n      exit(1);\n   }\n}\n\n/*! EINTR aware connect() call */\nint ckd_connect (int sock_fd, struct sockaddr *addr, socklen_t len)\n{\n   ssize_t ret;\n\n   do {\n      ret = connect(sock_fd, addr, len);\n   } while (ret == -1 && errno == EINTR);\n   if (ret == -1) {\n      ERRMSG(\"dpid.c\", \"connect\", errno);\n   }\n   return ret;\n}\n\n/*! Send DpiBye command to all active non-filter dpis\n */\nvoid stop_active_dpis(struct dp *dpi_attr_list, int numdpis)\n{\n   char *bye_cmd, *auth_cmd;\n   int i, sock_fd;\n   struct sockaddr_in sin;\n\n   bye_cmd = a_Dpip_build_cmd(\"cmd=%s\", \"DpiBye\");\n   auth_cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"auth\", SharedKey);\n\n   memset(&sin, 0, sizeof(sin));\n   sin.sin_family = AF_INET;\n   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n\n   for (i = 0; i < numdpis; i++) {\n      /* Skip inactive dpis and filters */\n      if (dpi_attr_list[i].pid == 1 || dpi_attr_list[i].filter)\n         continue;\n\n      if ((sock_fd = make_socket_fd()) == -1) {\n         ERRMSG(\"stop_active_dpis\", \"socket\", errno);\n         continue;\n      }\n\n      sin.sin_port = htons(dpi_attr_list[i].port);\n      if (ckd_connect(sock_fd, (struct sockaddr *)&sin, sizeof(sin)) == -1) {\n         ERRMSG(\"stop_active_dpis\", \"connect\", errno);\n         MSG_ERR(\"%s\\n\", dpi_attr_list[i].path);\n      } else if (CKD_WRITE(sock_fd, auth_cmd) == -1) {\n         ERRMSG(\"stop_active_dpis\", \"write\", errno);\n      } else if (CKD_WRITE(sock_fd, bye_cmd) == -1) {\n         ERRMSG(\"stop_active_dpis\", \"write\", errno);\n      }\n      dClose(sock_fd);\n   }\n\n   dFree(auth_cmd);\n   dFree(bye_cmd);\n\n   /* Allow child dpis some time to read dpid_comm_keys before erasing it */\n   sleep (1);\n}\n\n/*! Removes dpis in dpi_attr_list from the\n * set of sockets watched by select and\n * closes their sockets.\n */\nvoid ignore_dpi_sockets(struct dp *dpi_attr_list, int numdpis)\n{\n   int i;\n\n   for (i = 0; i < numdpis; i++) {\n      FD_CLR(dpi_attr_list[i].sock_fd, &sock_set);\n      dClose(dpi_attr_list[i].sock_fd);\n   }\n}\n\n/*! Registers available dpis and stops active non-filter dpis.\n * Called when dpid receives\n * cmd='register' service='all'\n * command\n * \\Return\n * Number of available dpis\n */\nint register_all_cmd()\n{\n   stop_active_dpis(dpi_attr_list, numdpis);\n   free_plugin_list(&dpi_attr_list, numdpis);\n   free_services_list(services_list);\n   services_list = NULL;\n   numdpis = 0;\n   numsocks = 1;                /* the srs socket */\n   FD_ZERO(&sock_set);\n   FD_SET(srs_fd, &sock_set);\n   numdpis = register_all(&dpi_attr_list);\n   fill_services_list(dpi_attr_list, numdpis, &services_list);\n   numsocks = init_all_dpi_sockets(dpi_attr_list);\n   return (numdpis);\n}\n\n/*!\n * Get value of msg field from dpi_tag\n * \\Return\n * message on success, NULL on failure\n */\nchar *get_message(int sock_fd, char *dpi_tag)\n{\n   char *msg, *d_cmd;\n\n   msg = a_Dpip_get_attr(dpi_tag, \"msg\");\n   if (msg == NULL) {\n      ERRMSG(\"get_message\", \"failed to parse msg\", 0);\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\",\n                               \"DpiError\", \"Failed to parse request\");\n      (void) CKD_WRITE(sock_fd, d_cmd);\n      dFree(d_cmd);\n   }\n   return (msg);\n}\n\n/*\n * Compare a struct service pointer and a service name\n * This function is used for searching services by name\n */\nint service_match(const struct service *A, const char *B)\n{\n   int A_len, B_len, len;\n\n   A_len = strlen(A->name);\n   B_len = strlen(B);\n   len = MAX (A_len, B_len);\n\n   if (A->name[A_len - 1] == '*')\n      len = A_len - 1;\n\n   return(dStrnAsciiCasecmp(A->name, B, len));\n}\n\n/*!\n * Send socket port that matches dpi_id to client\n */\nvoid send_sockport(int sock_fd, char *dpi_tag, struct dp *dpi_attr_list)\n{\n   int i;\n   char *dpi_id, *d_cmd, port_str[16];\n   struct service *serv;\n\n   dReturn_if_fail((dpi_id = get_message(sock_fd, dpi_tag)) != NULL);\n\n   serv = dList_find_custom(services_list,dpi_id,(dCompareFunc)service_match);\n\n   if (serv == NULL || (i = serv->dp_index) == -1)\n      for (i = 0; i < numdpis; i++)\n         if (!strncmp(dpi_attr_list[i].id, dpi_id,\n                      dpi_attr_list[i].id - strchr(dpi_attr_list[i].id, '.')))\n            break;\n\n   if (i < numdpis) {\n      /* found */\n      snprintf(port_str, 8, \"%d\", dpi_attr_list[i].port);\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"send_data\", port_str);\n      (void) CKD_WRITE(sock_fd, d_cmd);\n      dFree(d_cmd);\n   }\n\n   dFree(dpi_id);\n}\n"
  },
  {
    "path": "dpid/dpid-plus.h",
    "content": "/*! \\file\n * Main functions to set-up dpi information and to initialise sockets\n */\n\n#ifndef DPID_H\n#define DPID_H\n\n#include <sys/socket.h>\n#include <sys/select.h>   /* for fd_set */\n#include <sys/un.h>\n#include <signal.h>       /* for sig_atomic_t */\n#include <netinet/in.h>   /* for ntohl, IPPORT_USERRESERVED and stuff */\n\n#include \"d_size.h\"\n\n/* FreeBSD 6.4 doesn't have it */\n#ifndef IPPORT_USERRESERVED\n #define IPPORT_USERRESERVED 5000\n#endif\n\n#define PATH_LEN 50\n#define CMDLEN 20\n#define MSGLEN 50\n#define DPID_BASE_PORT (IPPORT_USERRESERVED + 20)\n\n/*! \\TODO: Should read this from dillorc */\n#define SRS_NAME \"dpid-plus.srs\"\n//char *srs_name;\n\n/*! dpid's service request socket file descriptor */\nextern int srs_fd;\n\n/*! plugin state information\n */\nstruct dp {\n   char *id;\n   char *path;\n   int sock_fd;\n   int port;\n   pid_t pid;\n   int filter;\n};\n\n/*! bind dpi with service\n */\nstruct service {\n   char *name;\n   int dp_index;\n};\n\n/*! Number of available plugins */\nextern int numdpis;\n\n/*! Number of sockets being watched */\nextern int numsocks;\n\n/*! State information for each plugin. */\nextern struct dp *dpi_attr_list;\n\n/*! service served for each plugin  */\nextern Dlist *services_list;\n\n/*! Set of sockets watched for connections */\nextern fd_set sock_set;\n\n/*! Set to 1 by the SIGCHLD handler dpi_sigchld */\nextern volatile sig_atomic_t caught_sigchld;\n\nvoid rm_dpi_sockets(struct dp *dpi_attr_list, int numdpis);\n\nvoid cleanup();\n\nvoid free_dpi_attr(struct dp *dpi_attr);\n\nvoid free_plugin_list(struct dp **dpi_attr_list_ptr, int numdpis);\n\nvoid free_services_list(Dlist *s_list);\n\nenum file_type get_file_type(char *file_name);\n\nint get_dpi_attr(char *dpi_dir, char *service, struct dp *dpi_attr);\n\nint register_service(struct dp *dpi_attr, char *service);\n\nint register_all(struct dp **attlist);\n\nint fill_services_list(struct dp *attlist, int numdpis, Dlist **services_list);\n\nint init_ids_srs_socket();\n\nint init_dpi_socket(struct dp *dpi_attr);\n\nint init_all_dpi_sockets(struct dp *dpi_attr_list);\n\nvoid dpi_sigchld(int sig);\n\nvoid handle_sigchld(void);\n\nvoid est_dpi_sigchld(void);\n\nvoid est_dpi_terminator(void);\n\nvoid stop_active_dpis(struct dp *dpi_attr_list, int numdpis);\n\nvoid ignore_dpi_sockets(struct dp *dpi_attr_list, int numdpis);\n\nint register_all_cmd();\n\nchar *get_message(int sock, char *dpi_tag);\n\nint service_match(const struct service *A, const char *B);\n\nvoid send_sockport(int sock_fd, char * dpi_tag, struct dp *dpi_attr_list);\n\n#endif\n"
  },
  {
    "path": "dpid/dpid_common.c",
    "content": "/*\n * File: dpid_common.c\n *\n * Copyright 2008 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <errno.h>\n#include <stdio.h>\n#include <unistd.h>\n#include \"dpid_common.h\"\n\n/*\n * Send a verbose error message.\n */\nvoid errmsg(char *caller, char *called, int errornum, char *file, int line)\n{\n   MSG_ERR(\"%s:%d: %s: %s\\n\", file, line, caller, called);\n   if (errornum > 0)\n      MSG_ERR(\"%s\\n\", dStrerror(errornum));\n}\n\n/*!\n * Provides an error checked write command.\n * Call this via the CKD_WRITE macro\n * \\return write return value\n */\nssize_t ckd_write(int fd, char *msg, char *file, int line)\n{\n   ssize_t ret;\n\n   do {\n      ret = write(fd, msg, strlen(msg));\n   } while (ret == -1 && errno == EINTR);\n   if (ret == -1) {\n      MSG_ERR(\"%s:%d: write: %s\\n\", file, line, dStrerror(errno));\n   }\n   return (ret);\n}\n\n/*!\n * Provides an error checked close() call.\n * Call this via the CKD_CLOSE macro\n * \\return close return value\n */\nssize_t ckd_close(int fd, char *file, int line)\n{\n   ssize_t ret;\n\n   ret = dClose(fd);\n   if (ret == -1)\n      MSG_ERR(\"%s:%d: close: %s\\n\", file, line, dStrerror(errno));\n   return ret;\n}\n\n"
  },
  {
    "path": "dpid/dpid_common.h",
    "content": "#ifndef DPID_COMMON_H\n#define DPID_COMMON_H\n\n/*! \\file\n * Declares common functions, global variables, and types.\n *\n * \\todo\n * The dpid error codes will be used in\n * the next patch\n */\n\n#include <dirent.h>\n\n#include \"../dlib/dlib.h\"\n\n/*\n * Debugging macros\n */\n#define _MSG(...)\n#define MSG(...)  printf(\"[dpid-plus]: \" __VA_ARGS__)\n#define MSG_ERR(...)  fprintf(stderr, \"[dpid]: \" __VA_ARGS__)\n\n#define dotDILLO_DPI \".\" BINNAME \"/dpi\"\n#define dotDILLO_DPIDRC \".\" BINNAME \"/dpidrc\"\n#define dotDILLO_DPID_COMM_KEYS \".\" BINNAME \"/dpid_comm_keys\"\n\n#define ERRMSG(CALLER, CALLED, ERR)\\\n errmsg(CALLER, CALLED, ERR, __FILE__, __LINE__)\n#define _ERRMSG(CALLER, CALLED, ERR)\n\n\n/*!\n * Macros for calling ckd_write and ckd_close functions\n */\n#define CKD_WRITE(fd, msg) ckd_write(fd, msg, __FILE__, __LINE__)\n#define CKD_CLOSE(fd)      ckd_close(fd, __FILE__, __LINE__)\n\n\n/*! Error codes for dpid */\nenum dpi_errno_t {\n   no_errors,\n   dpid_srs_addrinuse /* dpid service request socket address already in use */\n};\n\nextern enum dpi_errno_t dpi_errno;\n\n/*! Intended for identifying dillo plugins\n * and related files\n */\nenum file_type {\n   DPI_FILE,                     /*! Any file name containing .dpi */\n   UNKNOWN_FILE\n};\n\n\nvoid errmsg(char *caller, char *called, int errornum, char *file, int line);\n\nssize_t ckd_write(int fd, char *msg, char *file, int line);\nssize_t ckd_close(int fd, char *file, int line);\n\n#endif\n"
  },
  {
    "path": "dpid/dpidc-plus.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>  /* for exit */\n#include <string.h>  /* for bzero */\n#include <unistd.h>  /* for read and write */\n#include <ctype.h>   /* for isxdigit */\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <netinet/in.h>\n#include <netdb.h>\n#include <errno.h>\n\n#include \"../dlib/dlib.h\"\n#include \"../dpip/dpip.h\"\n\n#define MSG_ERR(...) printf(\"** ERROR **: \" __VA_ARGS__);\n\nchar *CMD_REGISTER = \"<cmd='register_all' '>\";\nchar *CMD_STOP     = \"<cmd='DpiBye' '>\";\n\nstatic char SharedKey[32];\n\nstatic void print_usage(const char *prgname)\n{\n   fprintf(stderr,\"Control program for the Dillo plugin daemon\\n\"\n                  \"Usage: %s {stop|register|chat}\\n\\n\", prgname);\n}\n\nstatic void error(char *msg)\n{\n   perror(msg);\n   exit(1);\n}\n\n/*\n * Read dpid's communication keys from its saved file.\n * Return value: 1 on success, -1 on error.\n */\nstatic int Dpi_read_comm_keys(int *port)\n{\n   FILE *In;\n   char *fname, *rcline = NULL, *tail;\n   int i, ret = -1;\n\n   fname = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/dpid_comm_keys\", NULL);\n   if ((In = fopen(fname, \"r\")) == NULL) {\n      MSG_ERR(\"[Dpi_read_comm_keys] %s\\n\", dStrerror(errno));\n   } else if ((rcline = dGetline(In)) == NULL) {\n      MSG_ERR(\"[Dpi_read_comm_keys] empty file: %s\\n\", fname);\n   } else {\n      *port = strtol(rcline, &tail, 10);\n      for (i = 0; *tail && isxdigit(tail[i+1]); ++i)\n         SharedKey[i] = tail[i+1];\n      SharedKey[i] = 0;\n      ret = 1;\n   }\n   dFree(rcline);\n   dFree(fname);\n\n   return ret;\n}\n\nint main(int argc, char *argv[])\n{\n    int sockfd, portno, n;\n    struct sockaddr_in serv_addr;\n    char buffer[256];\n\n    if (argc != 2) {\n       print_usage(argv[0]);\n       exit(1);\n    }\n\n    /* Read dpid's port number from saved file */\n    if (Dpi_read_comm_keys(&portno) == -1) {\n       MSG_ERR(\"main: Can't read dpid's port number\\n\");\n       exit(1);\n    }\n\n    sockfd = socket(AF_INET, SOCK_STREAM, 0);\n    if (sockfd < 0)\n        error(\"ERROR opening socket\");\n    bzero((char *) &serv_addr, sizeof(serv_addr));\n    serv_addr.sin_family = AF_INET;\n    serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n\n    serv_addr.sin_port = htons(portno);\n    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)\n        error(\"ERROR connecting\");\n\n    snprintf(buffer, sizeof(buffer), \"<cmd='auth' msg='%s' '>\", SharedKey);\n    n = write(sockfd, buffer, strlen(buffer));\n    if (n < 0)\n         error(\"ERROR writing to socket\");\n\n    if (strcmp(argv[1], \"stop\") == 0) {\n       strcpy(buffer, CMD_STOP);\n    } else if (strcmp(argv[1], \"register\") == 0) {\n       strcpy(buffer, CMD_REGISTER);\n    } else if (strcmp(argv[1], \"chat\") == 0) {\n       printf(\"Please enter the message: \");\n       bzero(buffer,256);\n       if (fgets(buffer,255,stdin) == NULL)\n          MSG_ERR(\"dpidc-plus: Can't read the message\\n\");\n    } else {\n       MSG_ERR(\"main: Unknown operation '%s'\\n\", argv[1]);\n       print_usage(argv[0]);\n       exit(1);\n    }\n\n    n = write(sockfd,buffer,strlen(buffer));\n    if (n < 0)\n         error(\"ERROR writing to socket\");\n/*\n    bzero(buffer,256);\n    n = read(sockfd,buffer,255);\n    if (n < 0)\n         error(\"ERROR reading from socket\");\n    printf(\"%s\\n\",buffer);\n*/\n    dClose(sockfd);\n    return 0;\n}\n"
  },
  {
    "path": "dpid/dpidrc.in",
    "content": "dpi_dir=@libdir@/@binname@/dpi\n\nproto.file=file/file.dpi\nproto.zip=zip/zip.dpi\nproto.man=man/man.dpi\nproto.ftp=ftp/ftp.filter.dpi\nproto.gemini=gemini/gemini.filter.dpi\nproto.gopher=gopher/gopher.filter.dpi\nproto.data=datauri/datauri.filter.dpi\n"
  },
  {
    "path": "dpid/main.c",
    "content": "/*\n   Copyright (C) 2003  Ferdi Franceschini <ferdif@optusnet.com.au>\n\n   This program is free software; you can redistribute it and/or modify\n   it under the terms of the GNU General Public License as published by\n   the Free Software Foundation; either version 3 of the License, or\n   (at your option) any later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <errno.h>       /* for ckd_write */\n#include <unistd.h>      /* for ckd_write */\n#include <stdlib.h>      /* for exit */\n#include <assert.h>      /* for assert */\n#include <sys/stat.h>    /* for umask */\n\n#include \"dpid_common.h\"\n#include \"dpid-plus.h\"\n#include \"dpi.h\"\n#include \"dpi_socket_dir.h\"\n#include \"misc_new.h\"\n\n#include \"../dlib/dlib.h\"\n#include \"../dpip/dpip.h\"\n\nsigset_t mask_sigchld;\nenum dpi_errno_t dpi_errno;\nstruct dp *dpi_attr_list;\nDlist *services_list;\nint numdpis;\nint numsocks;\nfd_set sock_set;\nint srs_fd;\n\n\n/* Start a dpi filter plugin after accepting the pending connection\n * \\Return\n * \\li Child process ID on success\n * \\li 0 on failure\n */\nstatic int start_filter_plugin(struct dp dpi_attr)\n{\n   int newsock, old_stdout=-1, old_stdin=-1;\n   socklen_t csz;\n   struct sockaddr_un clnt_addr;\n   pid_t pid;\n\n   csz = (socklen_t) sizeof(clnt_addr);\n\n   newsock = accept(dpi_attr.sock_fd, (struct sockaddr *) &clnt_addr, &csz);\n   if (newsock == -1)\n      ERRMSG(\"start_plugin\", \"accept\", errno);\n\n   dup2(STDIN_FILENO, old_stdin);\n   if (dup2(newsock, STDIN_FILENO) == -1) {\n      ERRMSG(\"start_plugin\", \"dup2\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n\n   dup2(STDOUT_FILENO, old_stdout);\n   if (dup2(newsock, STDOUT_FILENO) == -1) {\n      ERRMSG(\"start_plugin\", \"dup2\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n   if ((pid = fork()) == -1) {\n      ERRMSG(\"main\", \"fork\", errno);\n      return 0;\n   }\n   if (pid == 0) {\n      /* Child, start plugin */\n      if (execl(dpi_attr.path, dpi_attr.path, (char*)NULL) == -1) {\n         ERRMSG(\"start_plugin\", \"execl\", errno);\n         MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n         exit(1);\n      }\n   }\n\n   /* Parent, Close sockets fix stdio and return pid */\n   if (dClose(newsock) == -1) {\n      ERRMSG(\"start_plugin\", \"close\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n   dClose(STDIN_FILENO);\n   dClose(STDOUT_FILENO);\n   dup2(old_stdin, STDIN_FILENO);\n   dup2(old_stdout, STDOUT_FILENO);\n   return pid;\n}\n\nstatic void start_server_plugin(struct dp dpi_attr)\n{\n   if (dup2(dpi_attr.sock_fd, STDIN_FILENO) == -1) {\n      ERRMSG(\"start_plugin\", \"dup2\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n   if (dClose(dpi_attr.sock_fd) == -1) {\n      ERRMSG(\"start_plugin\", \"close\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n   if (execl(dpi_attr.path, dpi_attr.path, (char*)NULL) == -1) {\n      ERRMSG(\"start_plugin\", \"execl\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n}\n\n/*!\n * Read service request from sock\n * \\Return\n * pointer to dynamically allocated request tag\n */\nstatic char *get_request(Dsh *sh)\n{\n   char *dpip_tag;\n\n   (void) sigprocmask(SIG_BLOCK, &mask_sigchld, NULL);\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   (void) sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);\n\n   return dpip_tag;\n}\n\n/*!\n * Get value of cmd field in dpi_tag\n * \\Return\n * command code on success, -1 on failure\n */\nstatic int get_command(Dsh *sh, char *dpi_tag)\n{\n   char *cmd, *d_cmd;\n   int COMMAND;\n\n   if (dpi_tag == NULL) {\n      _ERRMSG(\"get_command\", \"dpid tag is NULL\", 0);\n      return (-1);\n   }\n\n   cmd = a_Dpip_get_attr(dpi_tag, \"cmd\");\n\n   if (cmd == NULL) {\n      ERRMSG(\"get_command\", \"a_Dpip_get_attr\", 0);\n      MSG_ERR(\": dpid failed to parse cmd in %s\\n\", dpi_tag);\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\",\n                               \"DpiError\", \"Failed to parse request\");\n      a_Dpip_dsh_write_str(sh, 1, d_cmd);\n      dFree(d_cmd);\n      COMMAND = -1;\n   } else if (strcmp(\"auth\", cmd) == 0) {\n      COMMAND = AUTH_CMD;\n   } else if (strcmp(\"DpiBye\", cmd) == 0) {\n      COMMAND = BYE_CMD;\n   } else if (strcmp(\"check_server\", cmd) == 0) {\n      COMMAND = CHECK_SERVER_CMD;\n   } else if (strcmp(\"register_all\", cmd) == 0) {\n      COMMAND = REGISTER_ALL_CMD;\n   } else if (strcmp(\"register_service\", cmd) == 0) {\n      COMMAND = REGISTER_SERVICE_CMD;\n   } else {                     /* Error unknown command */\n      COMMAND = UNKNOWN_CMD;\n   }\n\n   dFree(cmd);\n   return (COMMAND);\n}\n\n/*\n * Check whether a dpi server is running\n */\nstatic int server_is_running(char *server_id)\n{\n   int i;\n\n   /* Search in the set of running servers */\n   for (i = 0; i < numdpis; i++) {\n      if (!dpi_attr_list[i].filter && dpi_attr_list[i].pid > 1 &&\n          strcmp(dpi_attr_list[i].id, server_id) == 0)\n         return 1;\n   }\n   return 0;\n}\n\n\n/*\n * Get MAX open FD limit (yes, it's tricky --Jcid).\n */\nstatic int get_open_max(void)\n{\n#ifdef OPEN_MAX\n   return OPEN_MAX;\n#else\n   int ret = sysconf(_SC_OPEN_MAX);\n   if (ret < 0)\n      ret = 256;\n   return ret;\n#endif\n}\n\n/*! \\todo\n * \\li Add a dpid_idle_timeout variable to dpidrc\n * \\bug Infinite loop if plugin crashes before it accepts a connection\n */\nint main(void)\n{\n   int i, n = 0, open_max;\n   int dpid_idle_timeout = 60 * 60; /* default, in seconds */\n   struct timeval select_timeout;\n   sigset_t mask_none;\n   fd_set selected_set;\n\n   dpi_attr_list = NULL;\n   services_list = NULL;\n   //daemon(0,0); /* Use 0,1 for feedback */\n   /* TODO: call setsid() ?? */\n\n   /* Allow read and write access, but only for the user.\n    * TODO: can this cause trouble with umount? */\n   umask(0077);\n   /* TODO: make dpid work on any directory. */\n   // chdir(\"/\");\n\n   /* close inherited file descriptors */\n   open_max = get_open_max();\n   for (i = 3; i < open_max; i++)\n      dClose(i);\n\n   /* this sleep used to unmask a race condition */\n   // sleep(2);\n\n   dpi_errno = no_errors;\n\n   /* Get list of available dpis */\n   numdpis = register_all(&dpi_attr_list);\n\n#if 0\n   /* Get name of socket directory */\n   dirname = a_Dpi_sockdir_file();\n   if ((sockdir = init_sockdir(dirname)) == NULL) {\n      ERRMSG(\"main\", \"init_sockdir\", 0);\n      MSG_ERR(\"Failed to create socket directory\\n\");\n      exit(1);\n   }\n#endif\n\n   /* Init and get services list */\n   fill_services_list(dpi_attr_list, numdpis, &services_list);\n\n   /* Remove any sockets that may have been leftover from a crash */\n   //cleanup();\n\n   /* Initialise sockets */\n   if ((numsocks = init_ids_srs_socket()) == -1) {\n      switch (dpi_errno) {\n      case dpid_srs_addrinuse:\n         MSG_ERR(\"dpid refuses to start, possibly because:\\n\");\n         MSG_ERR(\"\\t1) An instance of dpid is already running.\\n\");\n         MSG_ERR(\"\\t2) A previous dpid didn't clean up on exit.\\n\");\n         exit(1);\n      default:\n         //ERRMSG(\"main\", \"init_srs_socket failed\", 0);\n         ERRMSG(\"main\", \"init_ids_srs_socket failed\", 0);\n         exit(1);\n      }\n   }\n   numsocks = init_all_dpi_sockets(dpi_attr_list);\n   est_dpi_terminator();\n   est_dpi_sigchld();\n\n   (void) sigemptyset(&mask_sigchld);\n   (void) sigaddset(&mask_sigchld, SIGCHLD);\n   (void) sigemptyset(&mask_none);\n   (void) sigprocmask(SIG_SETMASK, &mask_none, NULL);\n\n   printf(\"dpid started\\n\");\n/* Start main loop */\n   while (1) {\n      do {\n         (void) sigprocmask(SIG_BLOCK, &mask_sigchld, NULL);\n         if (caught_sigchld) {\n            handle_sigchld();\n            caught_sigchld = 0;\n         }\n         (void) sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);\n         select_timeout.tv_sec = dpid_idle_timeout;\n         select_timeout.tv_usec = 0;\n         selected_set = sock_set;\n         n = select(FD_SETSIZE, &selected_set, NULL, NULL, &select_timeout);\n         if (n == 0) { /* select timed out, try to exit */\n            /* BUG: This is a workaround for dpid not to exit when the\n             * downloads server is active. The proper way to handle it is with\n             * a dpip command that asks the server whether it's busy.\n             * Note: the cookies server may lose session info too. */\n            if (server_is_running(\"downloads\"))\n               continue;\n\n            stop_active_dpis(dpi_attr_list, numdpis);\n            //cleanup();\n            exit(0);\n         }\n      } while (n == -1 && errno == EINTR);\n\n      if (n == -1) {\n         ERRMSG(\"main\", \"select\", errno);\n         exit(1);\n      }\n      /* If the service req socket is selected then service the req. */\n      if (FD_ISSET(srs_fd, &selected_set)) {\n         int sock_fd;\n         socklen_t sin_sz;\n         struct sockaddr_in sin;\n         char *req = NULL;\n\n         --n;\n         assert(n >= 0);\n         sin_sz = (socklen_t) sizeof(sin);\n         sock_fd = accept(srs_fd, (struct sockaddr *)&sin, &sin_sz);\n         if (sock_fd == -1) {\n            ERRMSG(\"main\", \"accept\", errno);\n            MSG_ERR(\"accept on srs socket failed\\n\");\n            MSG_ERR(\"service pending connections, and continue\\n\");\n         } else {\n            int command;\n            Dsh *sh;\n\n            sh = a_Dpip_dsh_new(sock_fd, sock_fd, 1024);\nread_next:\n            req = get_request(sh);\n            command = get_command(sh, req);\n            switch (command) {\n            case AUTH_CMD:\n               if (a_Dpip_check_auth(req) != -1) {\n                  dFree(req);\n                  goto read_next;\n               }\n               break;\n            case BYE_CMD:\n               stop_active_dpis(dpi_attr_list, numdpis);\n               //cleanup();\n               exit(0);\n               break;\n            case CHECK_SERVER_CMD:\n               send_sockport(sock_fd, req, dpi_attr_list);\n               break;\n            case REGISTER_ALL_CMD:\n               register_all_cmd();\n               break;\n            case UNKNOWN_CMD:\n               {\n               char *d_cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\",\n                                              \"DpiError\", \"Unknown command\");\n               (void) CKD_WRITE(sock_fd, d_cmd);\n               dFree(d_cmd);\n               ERRMSG(\"main\", \"Unknown command\", 0);\n               MSG_ERR(\" for request: %s\\n\", req);\n               break;\n               }\n            case -1:\n               _ERRMSG(\"main\", \"get_command failed\", 0);\n               break;\n            }\n            if (req)\n               free(req);\n            a_Dpip_dsh_close(sh);\n            a_Dpip_dsh_free(sh);\n         }\n      }\n\n      /* While there's a request on one of the plugin sockets\n       * find the matching plugin and start it. */\n      for (i = 0; n > 0 && i < numdpis; i++) {\n         if (FD_ISSET(dpi_attr_list[i].sock_fd, &selected_set)) {\n            --n;\n            assert(n >= 0);\n\n            if (dpi_attr_list[i].filter) {\n               /* start a dpi filter plugin and continue watching its socket\n                * for new connections */\n               (void) sigprocmask(SIG_SETMASK, &mask_none, NULL);\n               start_filter_plugin(dpi_attr_list[i]);\n            } else {\n               /* start a dpi server plugin but don't wait for new connections\n                * on its socket */\n               numsocks--;\n               assert(numsocks >= 0);\n               FD_CLR(dpi_attr_list[i].sock_fd, &sock_set);\n               if ((dpi_attr_list[i].pid = fork()) == -1) {\n                  ERRMSG(\"main\", \"fork\", errno);\n                  /* exit(1); */\n               } else if (dpi_attr_list[i].pid == 0) {\n                  /* child */\n                  (void) sigprocmask(SIG_SETMASK, &mask_none, NULL);\n                  start_server_plugin(dpi_attr_list[i]);\n               }\n            }\n         }\n      }\n   }\n}\n"
  },
  {
    "path": "dpid/main.c.orig",
    "content": "/*\n   Copyright (C) 2003  Ferdi Franceschini <ferdif@optusnet.com.au>\n\n   This program is free software; you can redistribute it and/or modify\n   it under the terms of the GNU General Public License as published by\n   the Free Software Foundation; either version 3 of the License, or\n   (at your option) any later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <errno.h>       /* for ckd_write */\n#include <unistd.h>      /* for ckd_write */\n#include <stdlib.h>      /* for exit */\n#include <assert.h>      /* for assert */\n#include <sys/stat.h>    /* for umask */\n\n#include \"dpid_common.h\"\n#include \"dpid.h\"\n#include \"dpi.h\"\n#include \"dpi_socket_dir.h\"\n#include \"misc_new.h\"\n\n#include \"../dlib/dlib.h\"\n#include \"../dpip/dpip.h\"\n\nsigset_t mask_sigchld;\n\n\n/* Start a dpi filter plugin after accepting the pending connection\n * \\Return\n * \\li Child process ID on success\n * \\li 0 on failure\n */\nstatic int start_filter_plugin(struct dp dpi_attr)\n{\n   int newsock, old_stdout=-1, old_stdin=-1;\n   socklen_t csz;\n   struct sockaddr_un clnt_addr;\n   pid_t pid;\n\n   csz = (socklen_t) sizeof(clnt_addr);\n\n   newsock = accept(dpi_attr.sock_fd, (struct sockaddr *) &clnt_addr, &csz);\n   if (newsock == -1)\n      ERRMSG(\"start_plugin\", \"accept\", errno);\n\n   dup2(STDIN_FILENO, old_stdin);\n   if (dup2(newsock, STDIN_FILENO) == -1) {\n      ERRMSG(\"start_plugin\", \"dup2\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n\n   dup2(STDOUT_FILENO, old_stdout);\n   if (dup2(newsock, STDOUT_FILENO) == -1) {\n      ERRMSG(\"start_plugin\", \"dup2\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n   if ((pid = fork()) == -1) {\n      ERRMSG(\"main\", \"fork\", errno);\n      return 0;\n   }\n   if (pid == 0) {\n      /* Child, start plugin */\n      if (execl(dpi_attr.path, dpi_attr.path, (char*)NULL) == -1) {\n         ERRMSG(\"start_plugin\", \"execl\", errno);\n         MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n         exit(1);\n      }\n   }\n\n   /* Parent, Close sockets fix stdio and return pid */\n   if (dClose(newsock) == -1) {\n      ERRMSG(\"start_plugin\", \"close\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n   dClose(STDIN_FILENO);\n   dClose(STDOUT_FILENO);\n   dup2(old_stdin, STDIN_FILENO);\n   dup2(old_stdout, STDOUT_FILENO);\n   return pid;\n}\n\nstatic void start_server_plugin(struct dp dpi_attr)\n{\n   if (dup2(dpi_attr.sock_fd, STDIN_FILENO) == -1) {\n      ERRMSG(\"start_plugin\", \"dup2\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n   if (dClose(dpi_attr.sock_fd) == -1) {\n      ERRMSG(\"start_plugin\", \"close\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n   if (execl(dpi_attr.path, dpi_attr.path, (char*)NULL) == -1) {\n      ERRMSG(\"start_plugin\", \"execl\", errno);\n      MSG_ERR(\"ERROR in child proc for %s\\n\", dpi_attr.path);\n      exit(1);\n   }\n}\n\n/*!\n * Read service request from sock\n * \\Return\n * pointer to dynamically allocated request tag\n */\nstatic char *get_request(Dsh *sh)\n{\n   char *dpip_tag;\n\n   (void) sigprocmask(SIG_BLOCK, &mask_sigchld, NULL);\n   dpip_tag = a_Dpip_dsh_read_token(sh, 1);\n   (void) sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);\n\n   return dpip_tag;\n}\n\n/*!\n * Get value of cmd field in dpi_tag\n * \\Return\n * command code on success, -1 on failure\n */\nstatic int get_command(Dsh *sh, char *dpi_tag)\n{\n   char *cmd, *d_cmd;\n   int COMMAND;\n\n   if (dpi_tag == NULL) {\n      _ERRMSG(\"get_command\", \"dpid tag is NULL\", 0);\n      return (-1);\n   }\n\n   cmd = a_Dpip_get_attr(dpi_tag, \"cmd\");\n\n   if (cmd == NULL) {\n      ERRMSG(\"get_command\", \"a_Dpip_get_attr\", 0);\n      MSG_ERR(\": dpid failed to parse cmd in %s\\n\", dpi_tag);\n      d_cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\",\n                               \"DpiError\", \"Failed to parse request\");\n      a_Dpip_dsh_write_str(sh, 1, d_cmd);\n      dFree(d_cmd);\n      COMMAND = -1;\n   } else if (strcmp(\"auth\", cmd) == 0) {\n      COMMAND = AUTH_CMD;\n   } else if (strcmp(\"DpiBye\", cmd) == 0) {\n      COMMAND = BYE_CMD;\n   } else if (strcmp(\"check_server\", cmd) == 0) {\n      COMMAND = CHECK_SERVER_CMD;\n   } else if (strcmp(\"register_all\", cmd) == 0) {\n      COMMAND = REGISTER_ALL_CMD;\n   } else if (strcmp(\"register_service\", cmd) == 0) {\n      COMMAND = REGISTER_SERVICE_CMD;\n   } else {                     /* Error unknown command */\n      COMMAND = UNKNOWN_CMD;\n   }\n\n   dFree(cmd);\n   return (COMMAND);\n}\n\n/*\n * Check whether a dpi server is running\n */\nstatic int server_is_running(char *server_id)\n{\n   int i;\n\n   /* Search in the set of running servers */\n   for (i = 0; i < numdpis; i++) {\n      if (!dpi_attr_list[i].filter && dpi_attr_list[i].pid > 1 &&\n          strcmp(dpi_attr_list[i].id, server_id) == 0)\n         return 1;\n   }\n   return 0;\n}\n\n\n/*\n * Get MAX open FD limit (yes, it's tricky --Jcid).\n */\nstatic int get_open_max(void)\n{\n#ifdef OPEN_MAX\n   return OPEN_MAX;\n#else\n   int ret = sysconf(_SC_OPEN_MAX);\n   if (ret < 0)\n      ret = 256;\n   return ret;\n#endif\n}\n\n/*! \\todo\n * \\li Add a dpid_idle_timeout variable to dpidrc\n * \\bug Infinite loop if plugin crashes before it accepts a connection\n */\nint main(void)\n{\n   int i, n = 0, open_max;\n   int dpid_idle_timeout = 60 * 60; /* default, in seconds */\n   struct timeval select_timeout;\n   sigset_t mask_none;\n   fd_set selected_set;\n\n   dpi_attr_list = NULL;\n   services_list = NULL;\n   //daemon(0,0); /* Use 0,1 for feedback */\n   /* TODO: call setsid() ?? */\n\n   /* Allow read and write access, but only for the user.\n    * TODO: can this cause trouble with umount? */\n   umask(0077);\n   /* TODO: make dpid work on any directory. */\n   // chdir(\"/\");\n\n   /* close inherited file descriptors */\n   open_max = get_open_max();\n   for (i = 3; i < open_max; i++)\n      dClose(i);\n\n   /* this sleep used to unmask a race condition */\n   // sleep(2);\n\n   dpi_errno = no_errors;\n\n   /* Get list of available dpis */\n   numdpis = register_all(&dpi_attr_list);\n\n#if 0\n   /* Get name of socket directory */\n   dirname = a_Dpi_sockdir_file();\n   if ((sockdir = init_sockdir(dirname)) == NULL) {\n      ERRMSG(\"main\", \"init_sockdir\", 0);\n      MSG_ERR(\"Failed to create socket directory\\n\");\n      exit(1);\n   }\n#endif\n\n   /* Init and get services list */\n   fill_services_list(dpi_attr_list, numdpis, &services_list);\n\n   /* Remove any sockets that may have been leftover from a crash */\n   //cleanup();\n\n   /* Initialise sockets */\n   if ((numsocks = init_ids_srs_socket()) == -1) {\n      switch (dpi_errno) {\n      case dpid_srs_addrinuse:\n         MSG_ERR(\"dpid refuses to start, possibly because:\\n\");\n         MSG_ERR(\"\\t1) An instance of dpid is already running.\\n\");\n         MSG_ERR(\"\\t2) A previous dpid didn't clean up on exit.\\n\");\n         exit(1);\n      default:\n         //ERRMSG(\"main\", \"init_srs_socket failed\", 0);\n         ERRMSG(\"main\", \"init_ids_srs_socket failed\", 0);\n         exit(1);\n      }\n   }\n   numsocks = init_all_dpi_sockets(dpi_attr_list);\n   est_dpi_terminator();\n   est_dpi_sigchld();\n\n   (void) sigemptyset(&mask_sigchld);\n   (void) sigaddset(&mask_sigchld, SIGCHLD);\n   (void) sigemptyset(&mask_none);\n   (void) sigprocmask(SIG_SETMASK, &mask_none, NULL);\n\n   printf(\"dpid started\\n\");\n/* Start main loop */\n   while (1) {\n      do {\n         (void) sigprocmask(SIG_BLOCK, &mask_sigchld, NULL);\n         if (caught_sigchld) {\n            handle_sigchld();\n            caught_sigchld = 0;\n         }\n         (void) sigprocmask(SIG_UNBLOCK, &mask_sigchld, NULL);\n         select_timeout.tv_sec = dpid_idle_timeout;\n         select_timeout.tv_usec = 0;\n         selected_set = sock_set;\n         n = select(FD_SETSIZE, &selected_set, NULL, NULL, &select_timeout);\n         if (n == 0) { /* select timed out, try to exit */\n            /* BUG: This is a workaround for dpid not to exit when the\n             * downloads server is active. The proper way to handle it is with\n             * a dpip command that asks the server whether it's busy.\n             * Note: the cookies server may lose session info too. */\n            if (server_is_running(\"downloads\"))\n               continue;\n\n            stop_active_dpis(dpi_attr_list, numdpis);\n            //cleanup();\n            exit(0);\n         }\n      } while (n == -1 && errno == EINTR);\n\n      if (n == -1) {\n         ERRMSG(\"main\", \"select\", errno);\n         exit(1);\n      }\n      /* If the service req socket is selected then service the req. */\n      if (FD_ISSET(srs_fd, &selected_set)) {\n         int sock_fd;\n         socklen_t sin_sz;\n         struct sockaddr_in sin;\n         char *req = NULL;\n\n         --n;\n         assert(n >= 0);\n         sin_sz = (socklen_t) sizeof(sin);\n         sock_fd = accept(srs_fd, (struct sockaddr *)&sin, &sin_sz);\n         if (sock_fd == -1) {\n            ERRMSG(\"main\", \"accept\", errno);\n            MSG_ERR(\"accept on srs socket failed\\n\");\n            MSG_ERR(\"service pending connections, and continue\\n\");\n         } else {\n            int command;\n            Dsh *sh;\n\n            sh = a_Dpip_dsh_new(sock_fd, sock_fd, 1024);\nread_next:\n            req = get_request(sh);\n            command = get_command(sh, req);\n            switch (command) {\n            case AUTH_CMD:\n               if (a_Dpip_check_auth(req) != -1) {\n                  dFree(req);\n                  goto read_next;\n               }\n               break;\n            case BYE_CMD:\n               stop_active_dpis(dpi_attr_list, numdpis);\n               //cleanup();\n               exit(0);\n               break;\n            case CHECK_SERVER_CMD:\n               send_sockport(sock_fd, req, dpi_attr_list);\n               break;\n            case REGISTER_ALL_CMD:\n               register_all_cmd();\n               break;\n            case UNKNOWN_CMD:\n               {\n               char *d_cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\",\n                                              \"DpiError\", \"Unknown command\");\n               (void) CKD_WRITE(sock_fd, d_cmd);\n               dFree(d_cmd);\n               ERRMSG(\"main\", \"Unknown command\", 0);\n               MSG_ERR(\" for request: %s\\n\", req);\n               break;\n               }\n            case -1:\n               _ERRMSG(\"main\", \"get_command failed\", 0);\n               break;\n            }\n            if (req)\n               free(req);\n            a_Dpip_dsh_close(sh);\n            a_Dpip_dsh_free(sh);\n         }\n      }\n\n      /* While there's a request on one of the plugin sockets\n       * find the matching plugin and start it. */\n      for (i = 0; n > 0 && i < numdpis; i++) {\n         if (FD_ISSET(dpi_attr_list[i].sock_fd, &selected_set)) {\n            --n;\n            assert(n >= 0);\n\n            if (dpi_attr_list[i].filter) {\n               /* start a dpi filter plugin and continue watching its socket\n                * for new connections */\n               (void) sigprocmask(SIG_SETMASK, &mask_none, NULL);\n               start_filter_plugin(dpi_attr_list[i]);\n            } else {\n               /* start a dpi server plugin but don't wait for new connections\n                * on its socket */\n               numsocks--;\n               assert(numsocks >= 0);\n               FD_CLR(dpi_attr_list[i].sock_fd, &sock_set);\n               if ((dpi_attr_list[i].pid = fork()) == -1) {\n                  ERRMSG(\"main\", \"fork\", errno);\n                  /* exit(1); */\n               } else if (dpi_attr_list[i].pid == 0) {\n                  /* child */\n                  (void) sigprocmask(SIG_SETMASK, &mask_none, NULL);\n                  start_server_plugin(dpi_attr_list[i]);\n               }\n            }\n         }\n      }\n   }\n}\n"
  },
  {
    "path": "dpid/misc_new.c",
    "content": "/*\n * File: misc_new.c\n *\n * Copyright 2008 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <errno.h>      /* errno, err-codes */\n#include <unistd.h>\n#include <time.h>\n#include <sys/stat.h>   /* stat */\n#include <stdlib.h>     /* rand, srand */\n\n#include \"../dlib/dlib.h\"\n#include \"dpid_common.h\"\n#include \"misc_new.h\"   /* for function prototypes */\n\n/*! Reads a dpi tag from a socket\n * \\li Continues after a signal interrupt\n * \\Return\n * Dstr pointer to tag on success, NULL on failure\n * \\important Caller is responsible for freeing the returned Dstr *\n */\nDstr *a_Misc_rdtag(int socket)\n{\n   char c = '\\0';\n   ssize_t rdlen;\n   Dstr *tag;\n\n   tag = dStr_sized_new(64);\n\n   errno = 0;\n\n   do {\n      rdlen = read(socket, &c, 1);\n      if (rdlen == -1 && errno != EINTR)\n         break;\n      dStr_append_c(tag, c);\n   } while (c != '>');\n\n   if (rdlen == -1) {\n      perror(\"a_Misc_rdtag\");\n      dStr_free(tag, TRUE);\n      return (NULL);\n   }\n   return (tag);\n}\n\n/*!\n * Read a dpi tag from sock\n * \\return\n * pointer to dynamically allocated request tag\n */\nchar *a_Misc_readtag(int sock)\n{\n   char *tag, c;\n   size_t i;\n   size_t taglen = 0, tagmem = 10;\n   ssize_t rdln = 1;\n\n   tag = NULL;\n   // new start\n   tag = (char *) dMalloc(tagmem + 1);\n   for (i = 0; (rdln = read(sock, &c, 1)) != 0; i++) {\n      if (i == tagmem) {\n         tagmem += tagmem;\n         tag = (char *) dRealloc(tag, tagmem + 1);\n      }\n      tag[i] = c;\n      taglen += rdln;\n      if (c == '>') {\n         tag[i + 1] = '\\0';\n         break;\n      }\n   }\n   // new end\n   if (rdln == -1) {\n      ERRMSG(\"a_Misc_readtag\", \"read\", errno);\n   }\n\n   return (tag);\n}\n\n/*! Reads a dpi tag from a socket without hanging on read.\n * \\li Continues after a signal interrupt\n * \\Return\n * \\li 1 on success\n * \\li 0 if input is not available within timeout microseconds.\n * \\li -1 on failure\n * \\important Caller is responsible for freeing the returned Dstr *\n */\n/* Is this useful?\nint a_Misc_nohang_rdtag(int socket, int timeout, Dstr **tag)\n{\n   int n_fd;\n   fd_set sock_set, select_set;\n   struct timeval tout;\n\n   FD_ZERO(&sock_set);\n   FD_SET(socket, &sock_set);\n\n   errno = 0;\n   do {\n      select_set = sock_set;\n      tout.tv_sec = 0;\n      tout.tv_usec = timeout;\n      n_fd = select(socket + 1, &select_set, NULL, NULL, &tout);\n   } while (n_fd == -1 && errno == EINTR);\n\n   if (n_fd == -1) {\n      MSG_ERR(\"%s:%d: a_Misc_nohang_rdtag: %s\\n\",\n              __FILE__, __LINE__, dStrerror(errno));\n      return(-1);\n   }\n   if (n_fd == 0) {\n      return(0);\n   } else {\n      *tag = a_Misc_rdtag(socket);\n      return(1);\n   }\n}\n*/\n\n/*\n * Alternative to mkdtemp().\n * Not as strong as mkdtemp, but enough for creating a directory.\n */\nchar *a_Misc_mkdtemp(char *template)\n{\n   for (;;) {\n      if (a_Misc_mkfname(template) && mkdir(template, 0700) == 0)\n         break;\n      if (errno == EEXIST)\n         continue;\n      return 0;\n   }\n   return template;\n}\n\n/*\n * Return a new, nonexistent file name from a template\n * (adapted from dietlibc; alternative to mkdtemp())\n */\nchar *a_Misc_mkfname(char *template)\n{\n   char *tmp = template + strlen(template) - 6;\n   int i;\n   uint_t random;\n   struct stat stat_buf;\n\n   if (tmp < template)\n      goto error;\n   for (i = 0; i < 6; ++i)\n      if (tmp[i] != 'X') {\n       error:\n         errno = EINVAL;\n         return 0;\n      }\n   srand((uint_t)(time(0) ^ getpid()));\n\n   for (;;) {\n      random = (unsigned) rand();\n      for (i = 0; i < 6; ++i) {\n         int hexdigit = (random >> (i * 5)) & 0x1f;\n\n         tmp[i] = hexdigit > 9 ? hexdigit + 'a' - 10 : hexdigit + '0';\n      }\n      if (stat(template, &stat_buf) == -1 && errno == ENOENT)\n         return template;\n\n      MSG_ERR(\"a_Misc_mkfname: another round for %s \\n\", template);\n   }\n}\n\n/*\n * Return a new, random hexadecimal string of 'nchar' characters.\n */\nchar *a_Misc_mksecret(int nchar)\n{\n   int i;\n   uint_t random;\n   char *secret = dNew(char, nchar + 1);\n\n   srand((uint_t)(time(0) ^ getpid()));\n   random = (unsigned) rand();\n   for (i = 0; i < nchar; ++i) {\n      int hexdigit = (random >> (i * 5)) & 0x0f;\n\n      secret[i] = hexdigit > 9 ? hexdigit + 'a' - 10 : hexdigit + '0';\n   }\n   secret[i] = 0;\n   MSG(\"a_Misc_mksecret: %s\\n\", secret);\n\n   return secret;\n}\n\n"
  },
  {
    "path": "dpid/misc_new.h",
    "content": "#ifndef MISC_NEW_H\n#define MISC_NEW_H\n\nDstr *a_Misc_rdtag(int socket);\nchar *a_Misc_readtag(int sock);\nchar *a_Misc_mkdtemp(char *template);\nchar *a_Misc_mkfname(char *template);\nchar *a_Misc_mksecret(int nchar);\n\n#endif\n"
  },
  {
    "path": "dpip/Makefile",
    "content": "include ../Makefile.options\n\nlibDpip.a: dpip.o\n\t$(AR) $(ARFLAGS) libDpip.a dpip.o\n\t$(RANLIB) libDpip.a\n\ndpip.o: dpip.h dpip.c\n\t$(COMPILE) -DBINNAME='\"$(BINNAME)\"' -c dpip.c\n\nall: libDpip.a\n\nclean:\n\trm -f *.o *.a\n\ninstall:\nuninstall:\n"
  },
  {
    "path": "dpip/dpip.c",
    "content": "/*\n * File: dpip.c\n *\n * Copyright 2005-2015 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdarg.h>\n#include <string.h>\n#include <ctype.h>\n#include <unistd.h>   /* for close */\n#include <fcntl.h>    /* for fcntl */\n\n#include \"dpip.h\"\n#include \"d_size.h\"\n\n#define RBUF_SZ 16*1024\n//#define RBUF_SZ 1\n\n#define DPIP_TAG_END            \" '>\"\n#define DPIP_MODE_SWITCH_TAG    \"cmd='start_send_page' \"\n#define MSG_ERR(...)            fprintf(stderr, \"[dpip]: \" __VA_ARGS__)\n\n/*\n * Local variables\n */\nstatic const char Quote = '\\'';\n\n/*\n * Basically the syntax of a dpip tag is:\n *\n *  \"<\"[*alpha] *(<name>\"=\"Quote<escaped_value>Quote) \" \"Quote\">\"\n *\n *   1.- No space is allowed around the \"=\" sign between a name and its value.\n *   2.- The Quote character is not allowed in <name>.\n *   3.- Attribute values stuff Quote as QuoteQuote.\n *\n * e.g. (with ' as Quote):\n *\n *   <a='b' b='c' '>                 OK\n *   <dpi a='b i' b='12' '>          OK\n *   <a='>' '>                       OK\n *   <a='ain''t no doubt' '>         OK\n *   <a='ain''t b=''no'' b='' doubt' '>    OK\n *   <a = '>' '>                     Wrong\n *\n * Notes:\n *\n *   Restriction #1 is for easy finding of end of tag (EOT=Space+Quote+>).\n *   Restriction #2 can be removed, but what for? ;)\n *   The functions here provide for this functionality.\n */\n\ntypedef enum {\n   SEEK_NAME,\n   MATCH_NAME,\n   SKIP_VALUE,\n   SKIP_QUOTE,\n   FOUND\n} DpipTagParsingState;\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Printf like function for building dpip commands.\n * It takes care of dpip escaping of its arguments.\n * NOTE : It ONLY accepts string parameters, and\n *        only one %s per parameter.\n */\nchar *a_Dpip_build_cmd(const char *format, ...)\n{\n   va_list argp;\n   char *p, *q, *s;\n   Dstr *cmd;\n\n   /* Don't allow Quote characters in attribute names */\n   if (strchr(format, Quote))\n      return NULL;\n\n   cmd = dStr_sized_new(64);\n   dStr_append_c(cmd, '<');\n   va_start(argp, format);\n   for (p = q = (char*)format; *q;  ) {\n      p = strstr(q, \"%s\");\n      if (!p) {\n         dStr_append(cmd, q);\n         break;\n      } else {\n         /* Copy format's part */\n         while (q != p)\n            dStr_append_c(cmd, *q++);\n         q += 2;\n\n         dStr_append_c(cmd, Quote);\n         /* Stuff-copy of argument */\n         s = va_arg (argp, char *);\n         for (  ; *s; ++s) {\n            dStr_append_c(cmd, *s);\n            if (*s == Quote)\n               dStr_append_c(cmd, *s);\n         }\n         dStr_append_c(cmd, Quote);\n      }\n   }\n   va_end(argp);\n   dStr_append_c(cmd, ' ');\n   dStr_append_c(cmd, Quote);\n   dStr_append_c(cmd, '>');\n\n   p = cmd->str;\n   dStr_free(cmd, FALSE);\n   return p;\n}\n\n/*\n * Task: given a tag, its size and an attribute name, return the\n * attribute value (stuffing of ' is removed here).\n *\n * Return value: the attribute value, or NULL if not present or malformed.\n */\nchar *a_Dpip_get_attr_l(const char *tag, size_t tagsize, const char *attrname)\n{\n   uint_t i, n = 0, found = 0;\n   const char *p, *q, *start;\n   char *r, *s, *val = NULL;\n   DpipTagParsingState state = SEEK_NAME;\n\n   if (!tag || !tagsize || !attrname || !*attrname)\n      return NULL;\n\n   for (i = 1; i < tagsize && !found; ++i) {\n      switch (state) {\n      case SEEK_NAME:\n         if (tag[i] == attrname[0] && (tag[i-1] == ' ' || tag[i-1] == '<')) {\n            n = 1;\n            state = MATCH_NAME;\n         } else if (tag[i] == Quote && tag[i-1] == '=')\n            state = SKIP_VALUE;\n         break;\n      case MATCH_NAME:\n         if (tag[i] == attrname[n])\n            ++n;\n         else if (tag[i] == '=' && !attrname[n])\n            state = FOUND;\n         else\n            state = SEEK_NAME;\n         break;\n      case SKIP_VALUE:\n         if (tag[i] == Quote)\n            state = (tag[i+1] == Quote) ? SKIP_QUOTE : SEEK_NAME;\n         break;\n      case SKIP_QUOTE:\n         state = SKIP_VALUE;\n         break;\n      case FOUND:\n         found = 1;\n         break;\n      }\n   }\n\n   if (found) {\n      p = start = tag + i;\n      while ((q = strchr(p, Quote)) && q[1] == Quote)\n         p = q + 2;\n      if (q && q[1] == ' ') {\n         val = dStrndup(start, (uint_t)(q - start));\n         for (r = s = val; (*r = *s); ++r, ++s)\n            if (s[0] == Quote && s[0] == s[1])\n               ++s;\n      }\n   }\n   return val;\n}\n\n/*\n * Task: given a tag and an attribute name, return its value.\n * Return value: the attribute value, or NULL if not present or malformed.\n */\nchar *a_Dpip_get_attr(const char *tag, const char *attrname)\n{\n   return (tag ? a_Dpip_get_attr_l(tag, strlen(tag), attrname) : NULL);\n}\n\n/*\n * Check whether the given 'auth' string equals what dpid saved.\n * Return value: 1 if equal, -1 otherwise\n */\nint a_Dpip_check_auth(const char *auth_tag)\n{\n   char SharedSecret[32];\n   FILE *In;\n   char *fname, *rcline = NULL, *tail, *cmd, *msg;\n   int i, port, ret = -1;\n\n   /* sanity checks */\n   if (!auth_tag ||\n       !(cmd = a_Dpip_get_attr(auth_tag, \"cmd\")) || strcmp(cmd, \"auth\") ||\n       !(msg = a_Dpip_get_attr(auth_tag, \"msg\"))) {\n      return ret;\n   }\n\n   fname = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/dpid_comm_keys\", NULL);\n   if ((In = fopen(fname, \"r\")) == NULL) {\n      MSG_ERR(\"[a_Dpip_check_auth] %s\\n\", dStrerror(errno));\n   } else if ((rcline = dGetline(In)) == NULL) {\n      MSG_ERR(\"[a_Dpip_check_auth] empty file: %s\\n\", fname);\n   } else {\n      port = strtol(rcline, &tail, 10);\n      if (tail && port != 0) {\n         for (i = 0; *tail && isxdigit(tail[i+1]); ++i)\n            SharedSecret[i] = tail[i+1];\n         SharedSecret[i] = 0;\n         if (strcmp(msg, SharedSecret) == 0)\n            ret = 1;\n      }\n   }\n   if (In)\n      fclose(In);\n   dFree(rcline);\n   dFree(fname);\n   dFree(msg);\n   dFree(cmd);\n\n   return ret;\n}\n\n/* --------------------------------------------------------------------------\n * Dpip socket API ----------------------------------------------------------\n */\n\n/*\n * Create and initialize a dpip socket handler\n */\nDsh *a_Dpip_dsh_new(int fd_in, int fd_out, int flush_sz)\n{\n   Dsh *dsh = dNew(Dsh, 1);\n\n   /* init descriptors and streams */\n   dsh->fd_in  = fd_in;\n   dsh->fd_out = fd_out;\n\n   /* init buffer */\n   dsh->wrbuf = dStr_sized_new(8 *1024);\n   dsh->rdbuf = dStr_sized_new(8 *1024);\n   dsh->flush_sz = flush_sz;\n   dsh->mode = DPIP_TAG;\n   if (fcntl(dsh->fd_in, F_GETFL) & O_NONBLOCK)\n      dsh->mode |= DPIP_NONBLOCK;\n   dsh->status = 0;\n\n   return dsh;\n}\n\n/*\n * Return value: 1..DataSize sent, -1 eagain, or -3 on big Error\n */\nstatic int Dpip_dsh_write(Dsh *dsh, int nb, const char *Data, int DataSize)\n{\n   int req_mode, old_flags = 0, st, ret = -3, sent = 0;\n\n   req_mode = (nb) ? DPIP_NONBLOCK : 0;\n   if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {\n      /* change mode temporarily... */\n      old_flags = fcntl(dsh->fd_out, F_GETFL);\n      fcntl(dsh->fd_out, F_SETFL,\n            (nb) ? O_NONBLOCK | old_flags : old_flags & ~O_NONBLOCK);\n   }\n\n   while (1) {\n      st = write(dsh->fd_out, Data + sent, DataSize - sent);\n      if (st < 0) {\n         if (errno == EINTR) {\n            continue;\n         } else if (errno == EAGAIN) {\n            dsh->status = DPIP_EAGAIN;\n            ret = -1;\n            break;\n         } else {\n            MSG_ERR(\"[Dpip_dsh_write] %s\\n\", dStrerror(errno));\n            dsh->status = DPIP_ERROR;\n            break;\n         }\n      } else {\n         sent += st;\n         if (nb || sent == DataSize) {\n            ret = sent;\n            break;\n         }\n      }\n   }\n\n   if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {\n      /* restore old mode */\n      fcntl(dsh->fd_out, F_SETFL, old_flags);\n   }\n\n   return ret;\n}\n\n/*\n * Streamed write to socket\n * Return: 0 on success, 1 on error.\n */\nint a_Dpip_dsh_write(Dsh *dsh, int flush, const char *Data, int DataSize)\n{\n   int ret = 1;\n\n   /* append to buf */\n   dStr_append_l(dsh->wrbuf, Data, DataSize);\n\n   if (!flush || dsh->wrbuf->len == 0)\n      return 0;\n\n   ret = Dpip_dsh_write(dsh, 0, dsh->wrbuf->str, dsh->wrbuf->len);\n   if (ret == dsh->wrbuf->len) {\n      dStr_truncate(dsh->wrbuf, 0);\n      ret = 0;\n   }\n\n   return ret;\n}\n\n/*\n * Return value: 0 on success or empty buffer,\n *               1..DataSize sent, -1 eagain, or -3 on big Error\n */\nint a_Dpip_dsh_tryflush(Dsh *dsh)\n{\n   int st;\n\n   if (dsh->wrbuf->len == 0) {\n      st = 0;\n   } else {\n      st = Dpip_dsh_write(dsh, 1, dsh->wrbuf->str, dsh->wrbuf->len);\n      if (st > 0) {\n         /* update internal buffer */\n         dStr_erase(dsh->wrbuf, 0, st);\n      }\n   }\n   return (dsh->wrbuf->len == 0) ? 0 : st;\n}\n\n/*\n * Return value: 1..DataSize sent, -1 eagain, or -3 on big Error\n */\nint a_Dpip_dsh_trywrite(Dsh *dsh, const char *Data, int DataSize)\n{\n   int st;\n\n   if ((st = Dpip_dsh_write(dsh, 1, Data, DataSize)) > 0) {\n      /* update internal buffer */\n      if (st < DataSize)\n         dStr_append_l(dsh->wrbuf, Data + st, DataSize - st);\n   }\n   return st;\n}\n\n/*\n * Convenience function.\n */\nint a_Dpip_dsh_write_str(Dsh *dsh, int flush, const char *str)\n{\n   return a_Dpip_dsh_write(dsh, flush, str, (int)strlen(str));\n}\n\n/*\n * Read raw data from the socket into our buffer in\n * either BLOCKING or NONBLOCKING mode.\n */\nstatic void Dpip_dsh_read(Dsh *dsh, int blocking)\n{\n   char buf[RBUF_SZ];\n   int req_mode, old_flags = 0, st, nb = !blocking;\n\n   dReturn_if (dsh->status == DPIP_ERROR || dsh->status == DPIP_EOF);\n\n   req_mode = (nb) ? DPIP_NONBLOCK : 0;\n   if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {\n      /* change mode temporarily... */\n      old_flags = fcntl(dsh->fd_in, F_GETFL);\n      fcntl(dsh->fd_in, F_SETFL,\n            (nb) ? O_NONBLOCK | old_flags : old_flags & ~O_NONBLOCK);\n   }\n\n   while (1) {\n      st = read(dsh->fd_in, buf, RBUF_SZ);\n      if (st < 0) {\n         if (errno == EINTR) {\n            continue;\n         } else if (errno == EAGAIN) {\n            dsh->status = DPIP_EAGAIN;\n            break;\n         } else {\n            MSG_ERR(\"[Dpip_dsh_read] %s\\n\", dStrerror(errno));\n            dsh->status = DPIP_ERROR;\n            break;\n         }\n      } else if (st == 0) {\n         dsh->status = DPIP_EOF;\n         break;\n      } else {\n         /* append to buf */\n         dStr_append_l(dsh->rdbuf, buf, st);\n         if (blocking)\n            break;\n      }\n   }\n\n   if ((dsh->mode & DPIP_NONBLOCK) != req_mode) {\n      /* restore old mode */\n      fcntl(dsh->fd_out, F_SETFL, old_flags);\n   }\n\n   /* assert there's no more data in the wire...\n    * (st < buf upon interrupt || st == buf and no more data) */\n   if (blocking)\n      Dpip_dsh_read(dsh, 0);\n}\n\n/*\n * Return a newlly allocated string with the next dpip token in the socket.\n * Return value: token string and length on success, NULL otherwise.\n * (useful for handling null characters in the data stream)\n */\nchar *a_Dpip_dsh_read_token2(Dsh *dsh, int blocking, int *DataSize)\n{\n   char *p, *ret = NULL;\n   *DataSize = 0;\n\n   /* Read all available data without blocking */\n   Dpip_dsh_read(dsh, 0);\n\n   /* switch mode upon request */\n   if (dsh->mode & DPIP_LAST_TAG)\n      dsh->mode = DPIP_RAW;\n\n   if (blocking) {\n      if (dsh->mode & DPIP_TAG) {\n         /* Only wait for data when the tag is incomplete */\n         if (!strstr(dsh->rdbuf->str, DPIP_TAG_END)) {\n             do {\n                Dpip_dsh_read(dsh, 1);\n                p = strstr(dsh->rdbuf->str, DPIP_TAG_END);\n             } while (!p && dsh->status == EAGAIN);\n         }\n\n      } else if (dsh->mode & DPIP_RAW) {\n         /* Wait for data when the buffer is empty and there's no ERR/EOF */\n         while (dsh->rdbuf->len == 0 &&\n                dsh->status != DPIP_ERROR && dsh->status != DPIP_EOF)\n            Dpip_dsh_read(dsh, 1);\n      }\n   }\n\n   if (dsh->mode & DPIP_TAG) {\n      /* return a full tag */\n      if ((p = strstr(dsh->rdbuf->str, DPIP_TAG_END))) {\n         ret = dStrndup(dsh->rdbuf->str, p - dsh->rdbuf->str + 3);\n         *DataSize = p - dsh->rdbuf->str + 3;\n         dStr_erase(dsh->rdbuf, 0, p - dsh->rdbuf->str + 3);\n         if (strstr(ret, DPIP_MODE_SWITCH_TAG))\n            dsh->mode |= DPIP_LAST_TAG;\n      }\n   } else {\n      /* raw mode, return what we have \"as is\" */\n      if (dsh->rdbuf->len > 0) {\n         ret = dStrndup(dsh->rdbuf->str, dsh->rdbuf->len);\n         *DataSize = dsh->rdbuf->len;\n         dStr_truncate(dsh->rdbuf, 0);\n      }\n   }\n\n   return ret;\n}\n\n/*\n * Return a newlly allocated string with the next dpip token in the socket.\n * Return value: token string on success, NULL otherwise\n */\nchar *a_Dpip_dsh_read_token(Dsh *dsh, int blocking)\n{\n   int token_size;\n\n   return a_Dpip_dsh_read_token2(dsh, blocking, &token_size);\n}\n\n/*\n * Close this socket for reading and writing.\n * (flush pending data)\n */\nvoid a_Dpip_dsh_close(Dsh *dsh)\n{\n   int st;\n\n   /* flush internal buffer */\n   a_Dpip_dsh_write(dsh, 1, \"\", 0);\n\n   /* close fds */\n   st = dClose(dsh->fd_in);\n   if (st < 0)\n      MSG_ERR(\"[a_Dpip_dsh_close] close: %s\\n\", dStrerror(errno));\n   if (dsh->fd_out != dsh->fd_in) {\n      st = dClose(dsh->fd_out);\n      if (st < 0)\n         MSG_ERR(\"[a_Dpip_dsh_close] close: %s\\n\", dStrerror(errno));\n   }\n}\n\n/*\n * Free the SockHandler structure\n */\nvoid a_Dpip_dsh_free(Dsh *dsh)\n{\n   dReturn_if (dsh == NULL);\n\n   dStr_free(dsh->wrbuf, 1);\n   dStr_free(dsh->rdbuf, 1);\n   dFree(dsh);\n}\n\n"
  },
  {
    "path": "dpip/dpip.h",
    "content": "/*\n * Library for dealing with dpip tags (dillo plugin protocol tags).\n */\n\n#ifndef __DPIP_H__\n#define __DPIP_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#include \"../dlib/dlib.h\"\n\n/*\n * Communication mode flags\n */\n#define   DPIP_TAG        1   /* Dpip tags in the socket */\n#define   DPIP_LAST_TAG   2   /* Dpip mode-switching tag */\n#define   DPIP_RAW        4   /* Raw data in the socket  */\n#define   DPIP_NONBLOCK   8   /* Nonblocking IO          */\n\ntypedef enum {\n   DPIP_EAGAIN,\n   DPIP_ERROR,\n   DPIP_EOF\n} DpipDshStatus;\n\n/*\n * Dpip socket handler type.\n */\ntypedef struct {\n   int fd_in;\n   int fd_out;\n   /* FILE *in;    --Unused. The stream functions block when reading. */\n   FILE *out;\n\n   Dstr *wrbuf;    /* write buffer */\n   Dstr *rdbuf;    /* read buffer */\n   int flush_sz;   /* max size before flush */\n\n   int mode;       /* mode flags: DPIP_TAG | DPIP_LAST_TAG | DPIP_RAW */\n   int status;     /* status code: DPIP_EAGAIN | DPIP_ERROR | DPIP_EOF */\n} Dsh;\n\n\n/*\n * Printf like function for building dpip commands.\n * It takes care of dpip escaping of its arguments.\n * NOTE : It ONLY accepts string parameters, and\n *        only one %s per parameter.\n */\nchar *a_Dpip_build_cmd(const char *format, ...);\n\n/*\n * Task: given a tag and an attribute name, return its value.\n *       (dpip character escaping is removed here)\n * Return value: the attribute value, or NULL if not present or malformed.\n */\nchar *a_Dpip_get_attr(const char *tag, const char *attrname);\nchar *a_Dpip_get_attr_l(const char *tag, size_t tagsize, const char *attrname);\n\nint a_Dpip_check_auth(const char *auth);\n\n/*\n * Dpip socket API\n */\nDsh *a_Dpip_dsh_new(int fd_in, int fd_out, int flush_sz);\nint a_Dpip_dsh_write(Dsh *dsh, int flush, const char *Data, int DataSize);\nint a_Dpip_dsh_write_str(Dsh *dsh, int flush, const char *str);\nint a_Dpip_dsh_tryflush(Dsh *dsh);\nint a_Dpip_dsh_trywrite(Dsh *dsh, const char *Data, int DataSize);\nchar *a_Dpip_dsh_read_token(Dsh *dsh, int blocking);\nchar *a_Dpip_dsh_read_token2(Dsh *dsh, int blocking, int *DataSize);\nvoid a_Dpip_dsh_close(Dsh *dsh);\nvoid a_Dpip_dsh_free(Dsh *dsh);\n\n#define a_Dpip_dsh_printf(sh, flush, ...)                   \\\n   D_STMT_START {                                           \\\n      Dstr *dstr = dStr_sized_new(128);                     \\\n      dStr_sprintf(dstr, __VA_ARGS__);                      \\\n      a_Dpip_dsh_write(sh, flush, dstr->str, dstr->len);    \\\n      dStr_free(dstr, 1);                                   \\\n   } D_STMT_END\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __DPIP_H__ */\n\n"
  },
  {
    "path": "dw/Makefile",
    "content": "include ../Makefile.options\n\nCXXFLAGS_EXTRA = -DDILLO_LIBDIR='\"$(DILLO_LIBDIR)\"'\n\nall: libDw-core.a libDw-fltk.a libDw-widgets.a\n\nlibDw-core.a: findtext.o imgrenderer.o iterator.o layout.o selection.o style.o types.o ui.o stackingcontextmgr.o tools.o widget.o\n\t$(AR) $(ARFLAGS) libDw-core.a findtext.o imgrenderer.o iterator.o layout.o selection.o style.o types.o ui.o stackingcontextmgr.o tools.o widget.o\n\t$(RANLIB) libDw-core.a\n\nfindtext.o: findtext.hh findtext.cc\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c findtext.cc\n\nimgrenderer.o: imgrenderer.hh imgrenderer.cc\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c imgrenderer.cc\n\niterator.o: iterator.cc iterator.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c iterator.cc\n\nlayout.o: layout.cc layout.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c layout.cc\n\nselection.o: selection.cc selection.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c selection.cc\n\nstyle.o: style.cc style.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c style.cc\n\ntypes.o: types.cc types.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c types.cc\n\nui.o: ui.cc ui.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c ui.cc\n\nwidget.o: widget.cc widget.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c widget.cc\n\nlibDw-fltk.a: libDw_fltk_a-fltkcomplexbutton.o libDw_fltk_a-fltkflatview.o libDw_fltk_a-fltkimgbuf.o libDw_fltk_a-fltkmisc.o libDw_fltk_a-fltkplatform.o libDw_fltk_a-fltkpreview.o libDw_fltk_a-fltkui.o libDw_fltk_a-fltkviewbase.o  libDw_fltk_a-fltkviewport.o\n\t$(AR) $(ARFLAGS) libDw-fltk.a libDw_fltk_a-fltkcomplexbutton.o libDw_fltk_a-fltkflatview.o libDw_fltk_a-fltkimgbuf.o libDw_fltk_a-fltkmisc.o libDw_fltk_a-fltkplatform.o libDw_fltk_a-fltkpreview.o libDw_fltk_a-fltkui.o libDw_fltk_a-fltkviewbase.o  libDw_fltk_a-fltkviewport.o\n\t$(RANLIB) libDw-fltk.a\n\nlibDw_fltk_a-fltkcomplexbutton.o: fltkcomplexbutton.cc fltkcomplexbutton.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkcomplexbutton.o fltkcomplexbutton.cc\n\nlibDw_fltk_a-fltkflatview.o: fltkflatview.cc fltkflatview.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkflatview.o fltkflatview.cc\n\nlibDw_fltk_a-fltkimgbuf.o: fltkimgbuf.cc fltkimgbuf.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkimgbuf.o fltkimgbuf.cc\n\nlibDw_fltk_a-fltkmisc.o: fltkmisc.cc fltkmisc.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkmisc.o fltkmisc.cc\n\nlibDw_fltk_a-fltkplatform.o: fltkplatform.cc fltkplatform.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkplatform.o fltkplatform.cc\n\nlibDw_fltk_a-fltkpreview.o: fltkpreview.cc fltkpreview.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkpreview.o fltkpreview.cc\n\nlibDw_fltk_a-fltkui.o: fltkui.cc fltkui.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkui.o fltkui.cc\n\nlibDw_fltk_a-fltkviewbase.o: fltkviewbase.cc fltkviewbase.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkviewbase.o fltkviewbase.cc\n\nlibDw_fltk_a-fltkviewport.o: fltkviewport.cc fltkviewport.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c -o libDw_fltk_a-fltkviewport.o fltkviewport.cc\n\nlibDw-widgets.a: alignedtablecell.o alignedtextblock.o bullet.o hyphenator.o image.o listitem.o oofawarewidget.o ooffloatsmgr.o oofposabslikemgr.o oofposabsmgr.o oofposabslikemgr.o oofposfixedmgr.o oofpositionedmgr.o oofposrelmgr.o oofawarewidget_iterator.o outofflowmgr.o regardingborder.o ruler.o selection.o simpletablecell.o stackingcontextmgr.o table_iterator.o table.o tablecell.o textblock.o textblock_iterator.o textblock_linebreaking.o tools.o widget.o\n\t$(AR) $(ARFLAGS) libDw-widgets.a alignedtablecell.o alignedtextblock.o bullet.o hyphenator.o image.o listitem.o oofawarewidget.o ooffloatsmgr.o oofposabslikemgr.o oofposabsmgr.o oofposabslikemgr.o oofposfixedmgr.o oofpositionedmgr.o oofposrelmgr.o oofawarewidget_iterator.o outofflowmgr.o regardingborder.o ruler.o selection.o simpletablecell.o stackingcontextmgr.o table_iterator.o table.o tablecell.o textblock.o textblock_iterator.o textblock_linebreaking.o tools.o widget.o\n\t$(RANLIB) libDw-widgets.a\n\nalignedtablecell.o: alignedtablecell.cc alignedtablecell.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c alignedtablecell.cc\n\nalignedtextblock.o: alignedtextblock.cc alignedtextblock.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c alignedtextblock.cc\n\nbullet.o: bullet.cc bullet.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c bullet.cc\n\nhyphenator.o: hyphenator.cc hyphenator.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c hyphenator.cc\n\nimage.o: image.cc image.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c image.cc\n\nlistitem.o: listitem.cc listitem.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c listitem.cc\n\noofawarewidget.o: oofawarewidget.cc oofawarewidget.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c oofawarewidget.cc\n\nooffloatsmgr.o: ooffloatsmgr.cc ooffloatsmgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c ooffloatsmgr.cc\n\noofposabslikemgr.o: oofposabslikemgr.cc oofposabslikemgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c oofposabslikemgr.cc\n\noofposabsmgr.o: oofposabsmgr.cc oofposabsmgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c oofposabsmgr.cc\n\noofposabslikemgr.o: oofposabslikemgr.cc oofposabslikemgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c oofposabslikemgr.cc\n\noofposfixedmgr.o: oofposfixedmgr.cc oofposfixedmgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c oofposfixedmgr.cc\n\noofpositionedmgr.o: oofpositionedmgr.cc oofpositionedmgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c oofpositionedmgr.cc\n\noofposrelmgr.o: oofposrelmgr.cc oofposrelmgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c oofposrelmgr.cc\n\noofawarewidget_iterator.o: oofawarewidget_iterator.cc oofawarewidget.hh ooffloatsmgr.hh oofposabsmgr.hh oofposfixedmgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c oofawarewidget_iterator.cc\n\noutofflowmgr.o: outofflowmgr.cc outofflowmgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c outofflowmgr.cc\n\nregardingborder.o: regardingborder.cc regardingborder.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c regardingborder.cc\n\nruler.o: ruler.cc ruler.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c ruler.cc\n\nselection.o: selection.cc selection.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c selection.cc\n\nsimpletablecell.o: simpletablecell.cc simpletablecell.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c simpletablecell.cc\n\nstackingcontextmgr.o: stackingcontextmgr.cc stackingcontextmgr.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c stackingcontextmgr.cc\n\ntable.o: table.cc table.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c table.cc\n\ntable_iterator.o: table_iterator.cc table.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c table_iterator.cc\n\ntablecell.o: tablecell.cc tablecell.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c tablecell.cc\n\ntextblock.o: textblock.cc textblock.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c textblock.cc\n\ntextblock_iterator.o: textblock_iterator.cc\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c textblock_iterator.cc\n\ntextblock_linebreaking.o: textblock_linebreaking.cc\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c textblock_linebreaking.cc\n\ntools.o: tools.cc tools.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) -c tools.cc\n\nclean:\n\trm -f *.a *.o\n\ninstall:\nuninstall:\n"
  },
  {
    "path": "dw/alignedtablecell.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"alignedtablecell.hh\"\n#include \"table.hh\"\n#include \"tablecell.hh\"\n#include \"../lout/debug.hh\"\n#include <stdio.h>\n\nusing namespace lout;\n\nnamespace dw {\n\nint AlignedTableCell::CLASS_ID = -1;\n\nAlignedTableCell::AlignedTableCell (AlignedTableCell *ref, bool limitTextWidth):\n   AlignedTextblock (limitTextWidth)\n{\n   DBG_OBJ_CREATE (\"dw::AlignedTableCell\");\n   registerName (\"dw::AlignedTableCell\", &CLASS_ID);\n\n   /** \\bug ignoreLine1OffsetSometimes does not work? */\n   //ignoreLine1OffsetSometimes = true;\n   charWordIndex = -1;\n   setRefTextblock (ref);\n   setButtonSensitive(true);\n}\n\nAlignedTableCell::~AlignedTableCell()\n{\n   DBG_OBJ_DELETE ();\n}\n\n\nbool AlignedTableCell::getAdjustMinWidth ()\n{\n   return tablecell::getAdjustMinWidth ();\n}\n\nbool AlignedTableCell::isBlockLevel ()\n{\n   return tablecell::isBlockLevel ();\n}\n\nbool AlignedTableCell::usesMaxGeneratorWidth ()\n{\n   return tablecell::usesMaxGeneratorWidth ();\n}\n\nint AlignedTableCell::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"AlignedTableCell::getAvailWidthOfChild\",\n                  \"%p, %s\", child, forceValue ? \"true\" : \"false\");\n\n   int width = tablecell::correctAvailWidthOfChild\n      (this, child, Textblock::getAvailWidthOfChild (child, forceValue),\n       forceValue);\n\n   DBG_OBJ_LEAVE ();\n   return width;\n}\n\nint AlignedTableCell::getAvailHeightOfChild (Widget *child, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"AlignedTableCell::getAvailHeightOfChild\",\n                  \"%p, %s\", child, forceValue ? \"true\" : \"false\");\n\n   int height = tablecell::correctAvailHeightOfChild\n      (this, child, Textblock::getAvailHeightOfChild (child, forceValue),\n       forceValue);\n\n   DBG_OBJ_LEAVE ();\n   return height;\n}\n\nvoid AlignedTableCell::correctRequisitionOfChild (Widget *child,\n                                                  core::Requisition\n                                                  *requisition,\n                                                  void (*splitHeightFun) (int,\n                                                                          int*,\n                                                                          int*),\n                                                  bool allowDecreaseWidth,\n                                                  bool allowDecreaseHeight)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"AlignedTableCell::correctRequisitionOfChild\",\n                  \"%p, %d * (%d + %d), ..., %s, %s\", child, requisition->width,\n                  requisition->ascent, requisition->descent,\n                  misc::boolToStr (allowDecreaseWidth),\n                  misc::boolToStr (allowDecreaseHeight));\n\n   AlignedTextblock::correctRequisitionOfChild (child, requisition,\n                                                splitHeightFun,\n                                                allowDecreaseWidth,\n                                                allowDecreaseHeight);\n   tablecell::correctCorrectedRequisitionOfChild (this, child, requisition,\n                                                  splitHeightFun,\n                                                  allowDecreaseWidth,\n                                                  allowDecreaseHeight);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid AlignedTableCell::correctExtremesOfChild (Widget *child,\n                                               core::Extremes *extremes,\n                                               bool useAdjustmentWidth)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"AlignedTableCell::correctExtremesOfChild\",\n                  \"%p, %d (%d) / %d (%d)\",\n                  child, extremes->minWidth, extremes->minWidthIntrinsic,\n                  extremes->maxWidth, extremes->maxWidthIntrinsic);\n\n   AlignedTextblock::correctExtremesOfChild (child, extremes,\n                                             useAdjustmentWidth);\n   tablecell::correctCorrectedExtremesOfChild (this, child, extremes,\n                                               useAdjustmentWidth);\n\n   DBG_OBJ_LEAVE ();\n}\n\nint AlignedTableCell::applyPerWidth (int containerWidth,\n                                     core::style::Length perWidth)\n{\n   return tablecell::applyPerWidth (this, containerWidth, perWidth);\n}\n\nint AlignedTableCell::applyPerHeight (int containerHeight,\n                                      core::style::Length perHeight)\n{\n   return tablecell::applyPerHeight (this, containerHeight, perHeight);\n}\n\nbool AlignedTableCell::adjustExtraSpaceWhenCorrectingRequisitionByOOF ()\n{\n   return tablecell::adjustExtraSpaceWhenCorrectingRequisitionByOOF ();\n}\n\nint AlignedTableCell::wordWrap(int wordIndex, bool wrapAll)\n{\n   Textblock::Word *word;\n   const char *p;\n\n   int ret = Textblock::wordWrap (wordIndex, wrapAll);\n\n   if (charWordIndex == -1) {\n      word = words->getRef (wordIndex);\n      if (word->content.type == core::Content::TEXT) {\n         if ((p = strchr (word->content.text,\n                          word->style->textAlignChar))) {\n            charWordIndex = wordIndex;\n            charWordPos = p - word->content.text + 1;\n         } else if (word->style->textAlignChar == ' ' &&\n                    word->content.space) {\n            charWordIndex = wordIndex + 1;\n            charWordPos = 0;\n         }\n      }\n   }\n\n   if (wordIndex == charWordIndex)\n      updateValue ();\n\n   return ret;\n}\n\nint AlignedTableCell::getValue ()\n{\n   Textblock::Word *word;\n   int i, wordIndex;\n   int w;\n\n   if (charWordIndex == -1)\n      wordIndex = words->size () -1;\n   else\n      wordIndex = charWordIndex;\n\n   w = 0;\n   for (i = 0; i < wordIndex; i++) {\n      word = words->getRef (i);\n      w += word->size.width + word->origSpace;\n   }\n\n   if (charWordIndex == -1) {\n      if (words->size () > 0) {\n         word = words->getRef (words->size () - 1);\n         w += word->size.width;\n      }\n   } else {\n      word = words->getRef (charWordIndex);\n      w += layout->textWidth (word->style->font, word->content.text,\n                              charWordPos);\n   }\n\n   return w;\n}\n\nvoid AlignedTableCell::setMaxValue (int maxValue, int value)\n{\n   line1Offset = maxValue - value;\n   queueResize (makeParentRefInFlow (0), true);\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/alignedtablecell.hh",
    "content": "#ifndef __DW_ALIGNEDTABLECELL_HH__\n#define __DW_ALIGNEDTABLECELL_HH__\n\n#include \"core.hh\"\n#include \"alignedtextblock.hh\"\n\nnamespace dw {\n\nclass AlignedTableCell: public AlignedTextblock\n{\nprivate:\n   int charWordIndex, charWordPos;\n\nprotected:\n   int getAvailWidthOfChild (Widget *child, bool forceValue);\n   int getAvailHeightOfChild (Widget *child, bool forceValue);\n\n   void correctRequisitionOfChild (Widget *child,\n                                   core::Requisition *requisition,\n                                   void (*splitHeightFun) (int, int*, int*),\n                                   bool allowDecreaseWidth,\n                                   bool allowDecreaseHeight);\n   void correctExtremesOfChild (Widget *child, core::Extremes *extremes,\n                                bool useAdjustmentWidth);\n\n   bool getAdjustMinWidth ();\n\n   bool adjustExtraSpaceWhenCorrectingRequisitionByOOF ();\n\n   int wordWrap (int wordIndex, bool wrapAll);\n\n   int getValue ();\n   void setMaxValue (int maxValue, int value);\n\npublic:\n   static int CLASS_ID;\n\n   AlignedTableCell(AlignedTableCell *ref, bool limitTextWidth);\n   ~AlignedTableCell();\n\n   int applyPerWidth (int containerWidth, core::style::Length perWidth);\n   int applyPerHeight (int containerHeight, core::style::Length perHeight);\n\n   bool isBlockLevel ();\n\n   bool usesMaxGeneratorWidth ();\n};\n\n} // namespace dw\n\n#endif // __DW_ALIGNEDTABLECELL_HH__\n"
  },
  {
    "path": "dw/alignedtextblock.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"alignedtextblock.hh\"\n#include \"../lout/debug.hh\"\n#include <stdio.h>\n\nnamespace dw {\n\nAlignedTextblock::List::List ()\n{\n   textblocks = new lout::misc::SimpleVector <AlignedTextblock*> (4);\n   values = new lout::misc::SimpleVector <int> (4);\n   maxValue = 0;\n   refCount = 0;\n}\n\nAlignedTextblock::List::~List ()\n{\n   delete textblocks;\n   delete values;\n}\n\nint AlignedTextblock::List::add(AlignedTextblock *textblock)\n{\n   textblocks->increase ();\n   values->increase ();\n   textblocks->set (textblocks->size () - 1, textblock);\n   refCount++;\n   return textblocks->size () - 1;\n}\n\nvoid AlignedTextblock::List::unref(int pos)\n{\n   assert (textblocks->get (pos) != NULL);\n   textblocks->set (pos, NULL);\n   refCount--;\n\n   if (refCount == 0)\n      delete this;\n}\n\nint AlignedTextblock::CLASS_ID = -1;\n\nAlignedTextblock::AlignedTextblock (bool limitTextWidth):\n   Textblock (limitTextWidth)\n{\n   DBG_OBJ_CREATE (\"dw::AlignedTextblock\");\n   registerName (\"dw::AlignedTextblock\", &CLASS_ID);\n}\n\nvoid AlignedTextblock::setRefTextblock (AlignedTextblock *ref)\n{\n   if (ref == NULL)\n      list = new List();\n   else\n      list = ref->list;\n\n   listPos = list->add (this);\n   updateValue ();\n}\n\nAlignedTextblock::~AlignedTextblock()\n{\n   list->unref (listPos);\n   DBG_OBJ_DELETE ();\n}\n\nvoid AlignedTextblock::updateValue ()\n{\n   if (list) {\n      list->setValue (listPos, getValue ());\n\n      if (list->getValue (listPos) > list->getMaxValue ()) {\n         // New value greater than current maximum -> apply it to others.\n         list->setMaxValue (list->getValue (listPos));\n\n         for (int i = 0; i < list->size (); i++)\n            if (list->getTextblock (i))\n               list->getTextblock (i)->setMaxValue (list->getMaxValue (),\n                                                    list->getValue (i));\n      } else {\n         /* No change, apply old max_value only to this page. */\n         setMaxValue (list->getMaxValue (), list->getValue (listPos));\n      }\n   }\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/alignedtextblock.hh",
    "content": "#ifndef __DW_ALIGNEDTEXTBLOCK_HH__\n#define __DW_ALIGNEDTEXTBLOCK_HH__\n\n#include \"core.hh\"\n#include \"textblock.hh\"\n\nnamespace dw {\n\n/**\n * \\brief Base widget for all textblocks (sub classes of dw::Textblock), which\n *    are positioned vertically and aligned horizontally.\n */\nclass AlignedTextblock: public Textblock\n{\nprivate:\n   class List\n   {\n   private:\n      lout::misc::SimpleVector <AlignedTextblock*> *textblocks;\n      lout::misc::SimpleVector <int> *values;\n      int maxValue, refCount;\n\n      ~List ();\n\n   public:\n      List ();\n      inline int add (AlignedTextblock *textblock);\n      void unref (int pos);\n\n      inline int getMaxValue () { return maxValue; }\n      inline void setMaxValue (int maxValue) { this->maxValue = maxValue; }\n\n      inline int size () { return textblocks->size (); }\n      inline AlignedTextblock *getTextblock (int pos) {\n         return textblocks->get (pos); }\n      inline int getValue (int pos) {return values->get (pos); }\n      inline void setValue (int pos, int value) {\n         return values->set (pos, value); }\n   };\n\n   List *list;\n   int listPos;\n\nprotected:\n   AlignedTextblock(bool limitTextWidth);\n\n   virtual int getValue () = 0;\n   virtual void setMaxValue (int maxValue, int value) = 0;\n\n   void setRefTextblock (AlignedTextblock *ref);\n   void updateValue ();\n\npublic:\n   static int CLASS_ID;\n\n   ~AlignedTextblock();\n};\n\n} // namespace dw\n\n#endif // __DW_ALIGNEDTEXTBLOCK_HH__\n"
  },
  {
    "path": "dw/bullet.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"bullet.hh\"\n\n#include <stdio.h>\n\nnamespace dw {\n\nBullet::Bullet ()\n{\n   DBG_OBJ_CREATE (\"dw::Bullet\");\n}\n\nBullet::~Bullet ()\n{\n   DBG_OBJ_DELETE ();\n}\n\nvoid Bullet::sizeRequestSimpl (core::Requisition *requisition)\n{\n   requisition->width = lout::misc::max (getStyle()->font->xHeight * 4 / 5, 1);\n   requisition->ascent = lout::misc::max (getStyle()->font->xHeight, 1);\n   requisition->descent = 0;\n}\n\nvoid Bullet::getExtremesSimpl (core::Extremes *extremes)\n{\n   extremes->minWidth = extremes->maxWidth = extremes->adjustmentWidth =\n      extremes->minWidthIntrinsic = extremes->maxWidthIntrinsic =\n      lout::misc::max (getStyle()->font->xHeight * 4 / 5, 1);\n}\n\nvoid Bullet::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n   // Nothing to do.\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Bullet::draw (core::View *view, core::Rectangle *area,\n                   core::DrawingContext *context)\n{\n   int x, y, l;\n   bool filled = true;\n\n   l = lout::misc::min (allocation.width, allocation.ascent);\n   x = allocation.x;\n   y = allocation.y + allocation.ascent - getStyle()->font->xHeight;\n\n   switch (getStyle()->listStyleType) {\n   case core::style::LIST_STYLE_TYPE_SQUARE:\n      view->drawRectangle (getStyle()->color,\n                           core::style::Color::SHADING_NORMAL,\n                           false, x, y, l, l);\n      break;\n   case core::style::LIST_STYLE_TYPE_CIRCLE:\n      filled = false;\n      // Fall Through\n   case core::style::LIST_STYLE_TYPE_DISC:\n   default:\n      view->drawArc (getStyle()->color, core::style::Color::SHADING_NORMAL,\n                     filled, x + l/2, y + l/2, l, l, 0, 360);\n   }\n}\n\ncore::Iterator *Bullet::iterator (core::Content::Type mask, bool atEnd)\n{\n   //return new core::TextIterator (this, mask, atEnd, \"*\");\n   /** \\bug Not implemented. */\n   return new core::EmptyIterator (this, mask, atEnd);\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/bullet.hh",
    "content": "#ifndef __BULLET_HH__\n#define __BULLET_HH__\n\n#include \"core.hh\"\n\nnamespace dw {\n\n/**\n * \\brief Displays different kind of bullets.\n *\n * Perhaps, in the future, Unicode characters are used for bullets, so this\n * widget is not used anymore.\n */\nclass Bullet: public core::Widget\n{\nprotected:\n   void sizeRequestSimpl (core::Requisition *requisition);\n   void getExtremesSimpl (core::Extremes *extremes);\n   void containerSizeChangedForChildren ();\n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n   core::Iterator *iterator (core::Content::Type mask, bool atEnd);\n\npublic:\n   Bullet ();\n   ~Bullet ();\n};\n\n} // namespace dw\n\n#endif // __BULLET_HH__\n"
  },
  {
    "path": "dw/core.hh",
    "content": "#ifndef __DW_CORE_HH__\n#define __DW_CORE_HH__\n\n#define __INCLUDED_FROM_DW_CORE_HH__\n\n/**\n * \\brief Dw is in this namespace, or sub namespaces of this one.\n *\n * The core can be found in dw::core, widgets are defined directly here.\n *\n * \\sa \\ref dw-overview\n */\nnamespace dw {\n\n/** Used (temporally) for code related to positioned elements. */\nenum { IMPL_POS = false };\n\n/**\n * \\brief The core of Dw is defined in this namespace.\n *\n * \\sa \\ref dw-overview\n */\nnamespace core {\n\ntypedef unsigned char byte;\n\nclass Layout;\nclass View;\nclass Widget;\nclass Iterator;\nclass StackingContextMgr;\n\n// Nothing yet to free.\ninline void freeall () { }\n\nnamespace ui {\n\nclass ResourceFactory;\n\n} // namespace ui\n} // namespace core\n} // namespace dw\n\n#include \"../lout/object.hh\"\n#include \"../lout/container.hh\"\n#include \"../lout/signal.hh\"\n\n#include \"tools.hh\"\n#include \"types.hh\"\n#include \"events.hh\"\n#include \"imgbuf.hh\"\n#include \"imgrenderer.hh\"\n#include \"style.hh\"\n#include \"view.hh\"\n#include \"platform.hh\"\n#include \"iterator.hh\"\n#include \"findtext.hh\"\n#include \"selection.hh\"\n#include \"layout.hh\"\n#include \"widget.hh\"\n#include \"stackingcontextmgr.hh\"\n#include \"ui.hh\"\n\n#undef __INCLUDED_FROM_DW_CORE_HH__\n\n#endif // __DW_CORE_HH__\n"
  },
  {
    "path": "dw/events.hh",
    "content": "#ifndef __DW_EVENTS_HH__\n#define __DW_EVENTS_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief Platform independent representation.\n */\nenum ButtonState\n{\n   /* We won't use more than these ones. */\n   SHIFT_MASK    = 1 << 0,\n   CONTROL_MASK  = 1 << 1,\n   META_MASK     = 1 << 2,\n   BUTTON1_MASK  = 1 << 3,\n   BUTTON2_MASK  = 1 << 4,\n   BUTTON3_MASK  = 1 << 5\n};\n\n/**\n * \\brief Base class for all events.\n *\n * The dw::core::Event hierarchy describes events in a platform independent\n * way.\n */\nclass Event: public lout::object::Object\n{\npublic:\n};\n\n/**\n * \\brief Base class for all mouse events.\n */\nclass MouseEvent: public Event\n{\npublic:\n   ButtonState state;\n};\n\n/**\n * \\brief Base class for all mouse events related to a specific position.\n */\nclass MousePositionEvent: public MouseEvent\n{\npublic:\n   int xCanvas, yCanvas, xWidget, yWidget;\n};\n\n/**\n * \\brief Represents a button press or release event.\n */\nclass EventButton: public MousePositionEvent\n{\npublic:\n   int numPressed; /* 1 for simple click, 2 for double click, etc. */\n   int button;\n};\n\n/**\n * \\brief Represents a mouse motion event.\n */\nclass EventMotion: public MousePositionEvent\n{\n};\n\n/**\n * \\brief Represents a enter or leave notify event.\n */\nclass EventCrossing: public MouseEvent\n{\npublic:\n   Widget *lastWidget, *currentWidget;\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_EVENTS_HH__\n"
  },
  {
    "path": "dw/findtext.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"core.hh\"\n#include \"../lout/debug.hh\"\n#include \"../lout/msg.h\"\n\nnamespace dw {\nnamespace core {\n\nFindtextState::FindtextState ()\n{\n   DBG_OBJ_CREATE (\"dw::core::FindtextState\");\n\n   key = NULL;\n   nexttab = NULL;\n   widget = NULL;\n   iterator = NULL;\n   hlIterator = NULL;\n}\n\nFindtextState::~FindtextState ()\n{\n   if (key)\n      free(key);\n   if (nexttab)\n      delete[] nexttab;\n   if (iterator)\n      delete iterator;\n   if (hlIterator)\n      delete hlIterator;\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid FindtextState::setWidget (Widget *widget)\n{\n   this->widget = widget;\n\n   // A widget change will restart the search.\n   if (key)\n      free(key);\n   key = NULL;\n   if (nexttab)\n      delete[] nexttab;\n   nexttab = NULL;\n\n   if (iterator)\n      delete iterator;\n   iterator = NULL;\n   if (hlIterator)\n      delete hlIterator;\n   hlIterator = NULL;\n}\n\nFindtextState::Result FindtextState::search (const char *key, bool caseSens,\n                                             bool backwards)\n{\n   if (!widget || *key == 0) // empty keys are not found\n      return NOT_FOUND;\n\n   bool wasHighlighted = unhighlight ();\n   bool newKey;\n\n   // If the key (or the widget) changes (including case sensitivity),\n   // the search is started from the beginning.\n   if (this->key == NULL || this->caseSens != caseSens ||\n       strcmp (this->key, key) != 0) {\n      newKey = true;\n      if (this->key)\n         free(this->key);\n      this->key = strdup (key);\n      this->caseSens = caseSens;\n\n      if (nexttab)\n         delete[] nexttab;\n      nexttab = createNexttab (key, caseSens, backwards);\n\n      if (iterator)\n         delete iterator;\n      iterator = new CharIterator (widget, true);\n\n      if (backwards) {\n         /* Go to end */\n         while (iterator->next () ) ;\n         iterator->prev (); //We don't want to be at CharIterator::END.\n      } else {\n         iterator->next ();\n      }\n   } else\n      newKey = false;\n\n   bool firstTrial = !wasHighlighted || newKey;\n\n   if (search0 (backwards, firstTrial)) {\n      // Highlighting is done with a clone.\n      hlIterator = iterator->cloneCharIterator ();\n      for (int i = 0; key[i]; i++)\n         hlIterator->next ();\n      CharIterator::highlight (iterator, hlIterator, HIGHLIGHT_FINDTEXT);\n      CharIterator::scrollTo (iterator, hlIterator,\n                              HPOS_INTO_VIEW, VPOS_CENTER);\n\n      // The search will continue from the word after the found position.\n      iterator->next ();\n      return SUCCESS;\n   } else {\n      if (firstTrial) {\n         return NOT_FOUND;\n      } else {\n         // Nothing found anymore, reset the state for the next trial.\n         delete iterator;\n         iterator = new CharIterator (widget, true);\n         if (backwards) {\n            /* Go to end */\n            while (iterator->next ()) ;\n            iterator->prev (); //We don't want to be at CharIterator::END.\n         } else {\n            iterator->next ();\n         }\n         // We expect a success.\n         Result result2 = search (key, caseSens, backwards);\n         assert (result2 == SUCCESS);\n         return RESTART;\n      }\n   }\n}\n\n/**\n * \\brief This method is called when the user closes the \"find text\" dialog.\n */\nvoid FindtextState::resetSearch ()\n{\n   unhighlight ();\n\n   if (key)\n      free(key);\n   key = NULL;\n}\n\n/*\n * Return a new string: with the reverse of the original.\n */\nconst char* FindtextState::rev(const char *str)\n{\n   if (!str)\n      return NULL;\n\n   int len = strlen(str);\n   char *nstr = new char[len+1];\n   for (int i = 0; i < len; ++i)\n      nstr[i] = str[len-1 -i];\n   nstr[len] = 0;\n\n   return nstr;\n}\n\nint *FindtextState::createNexttab (const char *needle, bool caseSens,\n                                   bool backwards)\n{\n   const char* key;\n\n   key = (backwards) ? rev(needle) : needle;\n   int i = 0;\n   int j = -1;\n   int l = strlen (key);\n   int *nexttab = new int[l + 1]; // + 1 is necessary for l == 1 case\n   nexttab[0] = -1;\n\n   do {\n      if (j == -1 || charsEqual (key[i], key[j], caseSens)) {\n         i++;\n         j++;\n         nexttab[i] = j;\n         //_MSG (\"nexttab[%d] = %d\\n\", i, j);\n      } else\n         j = nexttab[j];\n   } while (i < l - 1);\n\n   if (backwards)\n      delete [] key;\n\n   return nexttab;\n}\n\n/**\n * \\brief Unhighlight, and return whether a region was highlighted.\n */\nbool FindtextState::unhighlight ()\n{\n   if (hlIterator) {\n      CharIterator *start = hlIterator->cloneCharIterator ();\n      for (int i = 0; key[i]; i++)\n         start->prev ();\n\n      CharIterator::unhighlight (start, hlIterator, HIGHLIGHT_FINDTEXT);\n      delete start;\n      delete hlIterator;\n      hlIterator = NULL;\n\n      return true;\n   } else\n      return false;\n}\n\nbool FindtextState::search0 (bool backwards, bool firstTrial)\n{\n   if (iterator->getChar () == CharIterator::END)\n      return false;\n\n   bool ret = false;\n   const char* searchKey = (backwards) ? rev(key) : key;\n   int j = 0;\n   bool nextit = true;\n   int l = strlen (key);\n\n   if (backwards && !firstTrial) {\n      _MSG(\"Having to do.\");\n      /* Position correctly */\n      /* In order to achieve good results (i.e: find a word that ends within\n       * the previously searched word's limit) we have to position the\n       * iterator in the semilast character of the previously searched word.\n       *\n       * Since we know that if a word was found before it was exactly the\n       * same word as the one we are searching for now, we can apply the\n       * following expression:\n       *\n       * Where l=length of the key and n=num of positions to move:\n       *\n       * n = l - 3\n       *\n       * If n is negative, we have to move backwards, but if it is\n       * positive, we have to move forward. So, when l>=4, we start moving\n       * the iterator forward. */\n\n      if (l==1) {\n         iterator->prev();\n         iterator->prev();\n      } else if (l==2) {\n         iterator->prev();\n      } else if (l>=4) {\n         for (int i=0; i<l-3; i++) {\n            iterator->next();\n         }\n      }\n\n   } else if (backwards && l==1) {\n      /* Particular case where we can't find the last character */\n      iterator->next();\n   }\n\n   do {\n      if (j == -1 || charsEqual (iterator->getChar(),searchKey[j],caseSens)) {\n         j++;\n         nextit = backwards ? iterator->prev () : iterator->next ();\n      } else\n         j = nexttab[j];\n   } while (nextit && j < l);\n\n   if (j >= l) {\n      if (backwards) {\n         //This is the location of the key\n         iterator->next();\n      } else {\n         // Go back to where the key was found.\n         for (int i = 0; i < l; i++)\n            iterator->prev ();\n      }\n      ret = true;\n   }\n\n   if (backwards)\n      delete [] searchKey;\n\n   return ret;\n}\n\n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/findtext.hh",
    "content": "#ifndef __DW_FINDTEXT_STATE_H__\n#define __DW_FINDTEXT_STATE_H__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\n#include <ctype.h>\n\nnamespace dw {\nnamespace core {\n\nclass FindtextState\n{\npublic:\n   typedef enum {\n      /** \\brief The next occurrence of the pattern has been found. */\n      SUCCESS,\n\n      /**\n       * \\brief There is no further occurrence of the pattern, instead, the\n       *    first occurrence has been selected.\n       */\n      RESTART,\n\n      /** \\brief The patten does not at all occur in the text. */\n      NOT_FOUND\n   } Result;\n\nprivate:\n   /**\n    * \\brief The key used for the last search.\n    *\n    * If dw::core::Findtext::search is called with the same key, the search\n    * is continued, otherwise it is restarted.\n    */\n   char *key;\n\n   /** \\brief Whether the last search was case sensitive. */\n   bool caseSens;\n\n   /** \\brief The table used for KMP search. */\n   int *nexttab;\n\n   /** \\brief The top of the widget tree, in which the search is done.\n    *\n    * From this, the iterator will be constructed. Set by\n    * dw::core::Findtext::widget\n    */\n   Widget *widget;\n\n   /** \\brief The position from where the next search will start. */\n   CharIterator *iterator;\n\n   /**\n    * \\brief The position from where the characters are highlighted.\n    *\n    * NULL, when no text is highlighted.\n    */\n   CharIterator *hlIterator;\n\n   static const char* rev(const char* _str); /* reverse a C string */\n\n   static int *createNexttab (const char *needle,bool caseSens,bool backwards);\n   bool unhighlight ();\n   bool search0 (bool backwards, bool firstTrial);\n\n   inline static bool charsEqual (char c1, char c2, bool caseSens)\n   { return caseSens ? c1 == c2 : tolower (c1) == tolower (c2) ||\n      (isspace (c1) && isspace (c2)); }\n\npublic:\n   FindtextState ();\n   ~FindtextState ();\n\n   void setWidget (Widget *widget);\n   Result search (const char *key, bool caseSens, bool backwards);\n   void resetSearch ();\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_FINDTEXT_STATE_H__\n"
  },
  {
    "path": "dw/fltkcomplexbutton.cc",
    "content": "// fltkcomplexbutton.cc is derived from src/Fl_Button.cxx from FLTK's 1.3\n// branch at http://fltk.org in early 2011.\n// src/Fl_Button.cxx is Copyright 1998-2010 by Bill Spitzak and others.\n\n/*\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"fltkcomplexbutton.hh\"\n\nusing namespace dw::fltk::ui;\n\n/**\n  Sets the current value of the button.\n  A non-zero value sets the button to 1 (ON), and zero sets it to 0 (OFF).\n  \\param[in] v button value.\n */\nint ComplexButton::value(int v) {\n  v = v ? 1 : 0;\n  oldval = v;\n  clear_changed();\n  if (value_ != v) {\n    value_ = v;\n    if (box()) redraw();\n    return 1;\n  } else {\n    return 0;\n  }\n}\n\nvoid ComplexButton::draw() {\n  Fl_Color col = value() ? selection_color() : color();\n  draw_box(value() ? (down_box()?down_box():fl_down(box())) : box(), col);\n\n  // ComplexButton is a Group; draw its children\n  for (int i = children () - 1; i >= 0; i--) {\n     // set absolute coordinates for fltk-1.3  --jcid\n     child (i)->position(x()+(w()-child(i)->w())/2,y()+(h()-child(i)->h())/2);\n     draw_child (*child (i));\n  }\n  if (Fl::focus() == this) draw_focus();\n}\n\nint ComplexButton::handle(int event) {\n  int newval;\n  switch (event) {\n  case FL_ENTER: /* FALLTHROUGH */\n  case FL_LEAVE:\n    return 1;\n  case FL_PUSH:\n    if (Fl::visible_focus() && handle(FL_FOCUS)) Fl::focus(this);\n  case FL_DRAG:\n    if (Fl::event_inside(this)) {\n      newval = !oldval;\n    } else\n    {\n      clear_changed();\n      newval = oldval;\n    }\n    if (newval != value_) {\n      value_ = newval;\n      set_changed();\n      redraw();\n    }\n    return 1;\n  case FL_RELEASE:\n    if (value_ == oldval) {\n      return 1;\n    }\n    set_changed();\n    value(oldval);\n    set_changed();\n    if (when() & FL_WHEN_RELEASE) do_callback();\n    return 1;\n  case FL_FOCUS : /* FALLTHROUGH */\n  case FL_UNFOCUS :\n    if (Fl::visible_focus()) {\n      redraw();\n      return 1;\n    } else return 0;\n  case FL_KEYBOARD :\n    if (Fl::focus() == this &&\n        (Fl::event_key() == ' ' || Fl::event_key() == FL_Enter) &&\n        !(Fl::event_state() & (FL_SHIFT | FL_CTRL | FL_ALT | FL_META))) {\n      value(1);\n      set_changed();\n      if (when() & FL_WHEN_RELEASE) do_callback();\n      return 1;\n    } else return 0;\n  case FL_KEYUP:\n    if (Fl::focus() == this &&\n        (Fl::event_key() == ' ' || Fl::event_key() == FL_Enter)) {\n      value(0);\n      return 1;\n    }\n  default:\n    return 0;\n  }\n}\n\n/**\n  The constructor creates the button using the given position, size and label.\n  \\param[in] X, Y, W, H position and size of the widget\n  \\param[in] L widget label, default is no label\n */\nComplexButton::ComplexButton(int X, int Y, int W, int H, const char *L)\n: Fl_Group(X,Y,W,H,L) {\n  Fl_Group::current(0);\n  box(FL_UP_BOX);\n  down_box(FL_NO_BOX);\n  value_ = oldval = 0;\n}\n\nComplexButton::~ComplexButton() {\n   /*\n    * The Fl_Group destructor clear()s the children, but layout expects\n    * the flat view to be around until it deletes it.\n    */\n   remove(0);\n}\n"
  },
  {
    "path": "dw/fltkcomplexbutton.hh",
    "content": "\n// fltkcomplexbutton.hh is derived from FL/Fl_Button.H from FLTK's 1.3 branch\n// at http://fltk.org in early 2011.\n// FL/Fl_Button.H is Copyright 1998-2010 by Bill Spitzak and others.\n\n/*\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#ifndef __FLTK_COMPLEX_BUTTON_HH__\n#define __FLTK_COMPLEX_BUTTON_HH__\n\n#include <FL/Fl_Group.H>\n\nnamespace dw {\nnamespace fltk {\nnamespace ui {\n\nclass ComplexButton : public Fl_Group {\n  char value_;\n  char oldval;\n  uchar down_box_;\n\nprotected:\n  virtual void draw();\n\npublic:\n  virtual int handle(int);\n\n  ComplexButton(int X, int Y, int W, int H, const char *L = 0);\n  ~ComplexButton();\n\n  int value(int v);\n\n  /**\n    Returns the current value of the button (0 or 1).\n   */\n  char value() const {return value_;}\n\n  /**\n    Returns the current down box type, which is drawn when value() is non-zero.\n    \\retval Fl_Boxtype\n   */\n  Fl_Boxtype down_box() const {return (Fl_Boxtype)down_box_;}\n\n  /**\n    Sets the down box type. The default value of 0 causes FLTK to figure out\n    the correct matching down version of box().\n    \\param[in] b down box type\n   */\n  void down_box(Fl_Boxtype b) {down_box_ = b;}\n};\n\n} // namespace ui\n} // namespace fltk\n} // namespace dw\n\n#endif\n\n//\n//\n"
  },
  {
    "path": "dw/fltkcore.hh",
    "content": "#ifndef __DW_FLTK_CORE_HH__\n#define __DW_FLTK_CORE_HH__\n\n#define __INCLUDED_FROM_DW_FLTK_CORE_HH__\n\nnamespace dw {\nnamespace fltk {\nnamespace ui {\n\nclass FltkResource;\n\n} // namespace ui\n} // namespace fltk\n} // namespace dw\n\n#include <FL/Fl_Widget.H>\n\n#include \"core.hh\"\n#include \"fltkimgbuf.hh\"\n#include \"fltkplatform.hh\"\n#include \"fltkui.hh\"\n\nnamespace dw {\nnamespace fltk {\n\ninline void freeall ()\n{\n   FltkImgbuf::freeall ();\n}\n\n} // namespace fltk\n} // namespace dw\n\n#undef __INCLUDED_FROM_DW_FLTK_CORE_HH__\n\n#endif // __DW_FLTK_CORE_HH__\n"
  },
  {
    "path": "dw/fltkflatview.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"fltkflatview.hh\"\n#include \"../lout/debug.hh\"\n\n#include <stdio.h>\n\nusing namespace lout::container::typed;\n\nnamespace dw {\nnamespace fltk {\n\nFltkFlatView::FltkFlatView (int x, int y, int w, int h, const char *label):\n   FltkWidgetView (x, y, w, h, label)\n{\n   DBG_OBJ_CREATE (\"dw::fltk::FltkFlatView\");\n}\n\nFltkFlatView::~FltkFlatView ()\n{\n}\n\nvoid FltkFlatView::setCanvasSize (int width, int ascent, int descent)\n{\n   /**\n    * \\bug It has to be clarified, who is responsible for setting the\n    *      FLTK widget size. In the only used context (complex buttons),\n    *      it is done elsewhere.\n    */\n\n#if 0\n   FltkWidgetView::setCanvasSize (width, ascent, descent);\n\n   w (width);\n   h (ascent + descent);\n#endif\n}\n\nbool FltkFlatView::usesViewport ()\n{\n   return false;\n}\n\nint FltkFlatView::getHScrollbarThickness ()\n{\n   return 0;\n}\n\nint FltkFlatView::getVScrollbarThickness ()\n{\n   return 0;\n}\n\nvoid FltkFlatView::scrollTo (int x, int y)\n{\n}\n\nvoid FltkFlatView::setViewportSize (int width, int height,\n                                    int hScrollbarThickness,\n                                    int vScrollbarThickness)\n{\n}\n\nint FltkFlatView::translateViewXToCanvasX (int X)\n{\n   return X - x ();\n}\n\nint FltkFlatView::translateViewYToCanvasY (int Y)\n{\n   return Y - y ();\n}\n\nint FltkFlatView::translateCanvasXToViewX (int X)\n{\n   return X + x ();\n}\n\nint FltkFlatView::translateCanvasYToViewY (int Y)\n{\n   return Y + y ();\n}\n\n\n} // namespace fltk\n} // namespace dw\n"
  },
  {
    "path": "dw/fltkflatview.hh",
    "content": "#ifndef __DW_FLTKFLATVIEW_HH__\n#define __DW_FLTKFLATVIEW_HH__\n\n#include \"core.hh\"\n#include \"fltkcore.hh\"\n#include \"fltkviewbase.hh\"\n\nnamespace dw {\nnamespace fltk {\n\nclass FltkFlatView: public FltkWidgetView\n{\nprotected:\n   int translateViewXToCanvasX (int x);\n   int translateViewYToCanvasY (int y);\n   int translateCanvasXToViewX (int x);\n   int translateCanvasYToViewY (int y);\n\npublic:\n   FltkFlatView (int x, int y, int w, int h, const char *label = 0);\n   ~FltkFlatView ();\n\n   void setCanvasSize (int width, int ascent, int descent);\n\n   bool usesViewport ();\n   int getHScrollbarThickness ();\n   int getVScrollbarThickness ();\n   void scrollTo (int x, int y);\n   void setViewportSize (int width, int height,\n                         int hScrollbarThickness, int vScrollbarThickness);\n};\n\n} // namespace fltk\n} // namespace dw\n\n#endif // __DW_FLTKFLATVIEW_HH__\n\n"
  },
  {
    "path": "dw/fltkimgbuf.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007, 2012-2013 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"fltkcore.hh\"\n#include \"../lout/msg.h\"\n#include \"../lout/misc.hh\"\n\n#include <FL/fl_draw.H>\n#include <math.h>\n\n#define IMAGE_MAX_AREA (6000 * 6000)\n\n#define MAX_WIDTH 0x8000\n#define MAX_HEIGHT 0x8000\n\nnamespace dw {\nnamespace fltk {\n\nusing namespace lout::container::typed;\n\nconst enum ScaleMode { SIMPLE, BEAUTIFUL, BEAUTIFUL_GAMMA }\n   scaleMode = BEAUTIFUL_GAMMA;\n\nVector <FltkImgbuf::GammaCorrectionTable> *FltkImgbuf::gammaCorrectionTables\n   = new Vector <FltkImgbuf::GammaCorrectionTable> (true, 2);\n\nuchar *FltkImgbuf::findGammaCorrectionTable (double gamma)\n{\n   // Since the number of possible keys is low, a linear search is\n   // sufficiently fast.\n\n   for (int i = 0; i < gammaCorrectionTables->size(); i++) {\n      GammaCorrectionTable *gct = gammaCorrectionTables->get(i);\n      if (gct->gamma == gamma)\n         return gct->map;\n   }\n\n   _MSG(\"Creating new table for gamma = %g\\n\", gamma);\n\n   GammaCorrectionTable *gct = new GammaCorrectionTable();\n   gct->gamma = gamma;\n\n   for (int i = 0; i < 256; i++)\n      gct->map[i] = 255 * pow((double)i / 255, gamma);\n\n   gammaCorrectionTables->put (gct);\n   return gct->map;\n}\n\nbool FltkImgbuf::excessiveImageDimensions (int width, int height)\n{\n   return width <= 0 || height <= 0 ||\n      width > IMAGE_MAX_AREA / height;\n}\n\nvoid FltkImgbuf::freeall ()\n{\n   _MSG(\"Deleting gammaCorrectionTables\\n\");\n   delete gammaCorrectionTables;\n   gammaCorrectionTables = NULL;\n}\n\nFltkImgbuf::FltkImgbuf (Type type, int width, int height, double gamma)\n{\n   DBG_OBJ_CREATE (\"dw::fltk::FltkImgbuf\");\n\n   _MSG (\"FltkImgbuf::FltkImgbuf: new root %p\\n\", this);\n   init (type, width, height, gamma, NULL);\n}\n\nFltkImgbuf::FltkImgbuf (Type type, int width, int height, double gamma,\n                        FltkImgbuf *root)\n{\n   DBG_OBJ_CREATE (\"dw::fltk::FltkImgbuf\");\n\n   _MSG (\"FltkImgbuf::FltkImgbuf: new scaled %p, root is %p\\n\", this, root);\n   init (type, width, height, gamma, root);\n}\n\nvoid FltkImgbuf::init (Type type, int width, int height, double gamma,\n                       FltkImgbuf *root)\n{\n   if (excessiveImageDimensions (width, height)) {\n      // Excessive image sizes which would cause crashes due to too\n      // big allocations for the image buffer (for root buffers, when\n      // the image was specially prepared). In this case we use a 1 x\n      // 1 size.\n      MSG(\"FltkImgbuf::init: suspicious image size request %d x %d\\n\",\n          width, height);\n      init (type, 1, 1, gamma, root);\n   } else if (width > MAX_WIDTH) {\n      // Too large dimensions cause dangerous overflow errors, so we\n      // limit dimensions to harmless values.\n      //\n      // Example: 65535 * 65536 / 65536 (see scaling below) results in\n      // the negative value -1.\n\n      MSG(\"FltkImgbuf::init: cannot handle large width %d\\n\", width);\n      init (type, MAX_WIDTH, height, gamma, root);\n   } else if (height > MAX_HEIGHT) {\n      MSG(\"FltkImgbuf::init: cannot handle large height %d\\n\", height);\n      init (type, width, MAX_HEIGHT, gamma, root);\n   } else if (gamma <= 0) {\n      MSG(\"FltkImgbuf::init: non-positive gamma %g\\n\", gamma);\n      init (type, width, height, 1, root);\n   } else {\n      this->root = root;\n      this->type = type;\n      this->width = width;\n      this->height = height;\n      this->gamma = gamma;\n\n      DBG_OBJ_SET_NUM (\"width\", width);\n      DBG_OBJ_SET_NUM (\"height\", height);\n\n      // TODO: Maybe this is only for root buffers\n      switch (type) {\n      case RGBA: bpp = 4; break;\n      case RGB:  bpp = 3; break;\n      default:   bpp = 1; break;\n      }\n      _MSG(\"FltkImgbuf::init this=%p width=%d height=%d bpp=%d gamma=%g\\n\",\n           this, width, height, bpp, gamma);\n      rawdata = new uchar[bpp * width * height];\n      // Set light-gray as interim background color.\n      memset(rawdata, 222, width*height*bpp);\n\n      refCount = 1;\n      deleteOnUnref = true;\n      copiedRows = new lout::misc::BitSet (height);\n\n      DBG_IF_RTFL {\n         lout::misc::StringBuffer sb;\n         copiedRows->intoStringBuffer (&sb);\n         DBG_OBJ_SET_SYM (\"copiedRows\", sb.getChars ());\n      }\n\n      // The list is only used for root buffers.\n      if (isRoot())\n         scaledBuffers = new lout::container::typed::List <FltkImgbuf> (true);\n      else\n         scaledBuffers = NULL;\n\n      if (!isRoot()) {\n         // Scaling\n         for (int row = 0; row < root->height; row++) {\n            if (root->copiedRows->get (row))\n               scaleRow (row, root->rawdata + row*root->width*root->bpp);\n         }\n      }\n   }\n}\n\nFltkImgbuf::~FltkImgbuf ()\n{\n   _MSG (\"FltkImgbuf::~FltkImgbuf\\n\");\n\n   if (!isRoot())\n      root->detachScaledBuf (this);\n\n   delete[] rawdata;\n   delete copiedRows;\n\n   if (scaledBuffers)\n      delete scaledBuffers;\n\n   DBG_OBJ_DELETE ();\n}\n\n/**\n * \\brief This method is called for the root buffer, when a scaled buffer\n *    removed.\n */\nvoid FltkImgbuf::detachScaledBuf (FltkImgbuf *scaledBuf)\n{\n   scaledBuffers->detachRef (scaledBuf);\n\n   _MSG(\"FltkImgbuf[root %p]: scaled buffer %p is detached, %d left\\n\",\n        this, scaledBuf, scaledBuffers->size ());\n\n   if (refCount == 0 && scaledBuffers->isEmpty () && deleteOnUnref)\n      // If the root buffer is not used anymore, but this is the last scaled\n      // buffer.\n      // See also: FltkImgbuf::unref().\n      delete this;\n}\n\nvoid FltkImgbuf::setCMap (int *colors, int num_colors)\n{\n}\n\ninline void FltkImgbuf::scaleRow (int row, const core::byte *data)\n{\n   if (row < root->height) {\n      if (scaleMode == SIMPLE)\n         scaleRowSimple (row, data);\n      else\n         scaleRowBeautiful (row, data);\n   }\n}\n\ninline void FltkImgbuf::scaleRowSimple (int row, const core::byte *data)\n{\n   int sr1 = scaledY (row);\n   int sr2 = scaledY (row + 1);\n\n   for (int sr = sr1; sr < sr2; sr++) {\n      // Avoid multiple passes.\n      if (copiedRows->get(sr)) continue;\n\n      copiedRows->set (sr, true);\n\n      DBG_IF_RTFL {\n         lout::misc::StringBuffer sb;\n         copiedRows->intoStringBuffer (&sb);\n         DBG_OBJ_SET_SYM (\"copiedRows\", sb.getChars ());\n      }\n\n      if (sr == sr1) {\n         for (int px = 0; px < root->width; px++) {\n            int px1 = px * width / root->width;\n            int px2 = (px+1) * width / root->width;\n            for (int sp = px1; sp < px2; sp++) {\n               memcpy(rawdata + (sr*width + sp)*bpp, data + px*bpp, bpp);\n            }\n         }\n      } else {\n         memcpy(rawdata + sr*width*bpp, rawdata + sr1*width*bpp, width*bpp);\n      }\n   }\n}\n\ninline void FltkImgbuf::scaleRowBeautiful (int row, const core::byte *data)\n{\n   int sr1 = scaledY (row);\n   int sr2 = scaledY (row + 1);\n   bool allRootRows = false;\n\n   // Don't rescale rows!\n   if (copiedRows->get(sr1)) return;\n\n   if (height > root->height) {\n      scaleBuffer (data, root->width, 1,\n                   rawdata + sr1 * width * bpp, width, sr2 - sr1,\n                   bpp, gamma);\n      // Mark scaled rows done\n      for (int sr = sr1; sr < sr2 || sr == sr1; sr++)\n         copiedRows->set (sr, true);\n\n      DBG_IF_RTFL {\n         lout::misc::StringBuffer sb;\n         copiedRows->intoStringBuffer (&sb);\n         DBG_OBJ_SET_SYM (\"copiedRows\", sb.getChars ());\n      }\n   } else {\n      assert (sr1 == sr2 || sr1 + 1 == sr2);\n      int row1 = backscaledY(sr1), row2 = backscaledY(sr1 + 1);\n\n      // Check all the necessary root lines already arrived,\n      // a larger area than a single row may be accessed here.\n      for (int r=row1; (allRootRows=root->copiedRows->get(r)) && ++r < row2; );\n      if (allRootRows) {\n         scaleBuffer (root->rawdata + row1 * root->width * bpp,\n                      root->width, row2 - row1,\n                      rawdata + sr1 * width * bpp, width, 1,\n                      bpp, gamma);\n         // Mark scaled row done\n         copiedRows->set (sr1, true);\n\n         DBG_IF_RTFL {\n            lout::misc::StringBuffer sb;\n            copiedRows->intoStringBuffer (&sb);\n            DBG_OBJ_SET_SYM (\"copiedRows\", sb.getChars ());\n         }\n      }\n   }\n}\n\n/**\n * General method to scale an image buffer. Used to scale single lines\n * in scaleRowBeautiful.\n *\n * The algorithm is rather simple. If the scaled buffer is smaller\n * (both width and height) than the original buffer, each pixel in the\n * scaled buffer is assigned a rectangle of pixels in the original\n * buffer; the resulting pixel value (red, green, blue) is simply the\n * average of all pixel values. This is pretty fast and leads to\n * rather good results.\n *\n * Nothing special (like interpolation) is done when scaling up.\n *\n * If scaleMode is set to BEAUTIFUL_GAMMA, gamma correction is\n * considered, see <http://www.4p8.com/eric.brasseur/gamma.html>.\n *\n * TODO Could be optimized as in scaleRowSimple: when the destination\n * image is larger, calculate only one row/column, and copy it to the\n * other rows/columns.\n */\ninline void FltkImgbuf::scaleBuffer (const core::byte *src, int srcWidth,\n                                     int srcHeight, core::byte *dest,\n                                     int destWidth, int destHeight, int bpp,\n                                     double gamma)\n{\n   uchar *gammaMap1, *gammaMap2;\n\n   if (scaleMode == BEAUTIFUL_GAMMA) {\n      gammaMap1 = findGammaCorrectionTable (gamma);\n      gammaMap2 = findGammaCorrectionTable (1 / gamma);\n   }\n\n   for(int x = 0; x < destWidth; x++)\n      for(int y = 0; y < destHeight; y++) {\n         int xo1 = x * srcWidth / destWidth;\n         int xo2 = lout::misc::max ((x + 1) * srcWidth / destWidth, xo1 + 1);\n         int yo1 = y * srcHeight / destHeight;\n         int yo2 = lout::misc::max ((y + 1) * srcHeight / destHeight, yo1 + 1);\n         int n = (xo2 - xo1) * (yo2 - yo1);\n\n         int v[bpp];\n         for(int i = 0; i < bpp; i++)\n            v[i] = 0;\n\n         for(int xo = xo1; xo < xo2; xo++)\n            for(int yo = yo1; yo < yo2; yo++) {\n               const core::byte *ps = src + bpp * (yo * srcWidth + xo);\n               for(int i = 0; i < bpp; i++)\n                  v[i] +=\n                     (scaleMode == BEAUTIFUL_GAMMA ? gammaMap2[ps[i]] : ps[i]);\n            }\n\n         core::byte *pd = dest + bpp * (y * destWidth + x);\n         for(int i = 0; i < bpp; i++)\n            pd[i] =\n               scaleMode == BEAUTIFUL_GAMMA ? gammaMap1[v[i] / n] : v[i] / n;\n      }\n}\n\nvoid FltkImgbuf::copyRow (int row, const core::byte *data)\n{\n   assert (isRoot());\n\n   if (row < height) {\n      // Flag the row done and copy its data.\n      copiedRows->set (row, true);\n\n      DBG_IF_RTFL {\n         lout::misc::StringBuffer sb;\n         copiedRows->intoStringBuffer (&sb);\n         DBG_OBJ_SET_SYM (\"copiedRows\", sb.getChars ());\n      }\n\n      memcpy(rawdata + row * width * bpp, data, width * bpp);\n\n      // Update all the scaled buffers of this root image.\n      for (Iterator <FltkImgbuf> it = scaledBuffers->iterator();\n           it.hasNext(); ) {\n         FltkImgbuf *sb = it.getNext ();\n         sb->scaleRow (row, data);\n      }\n   }\n}\n\nvoid FltkImgbuf::newScan ()\n{\n   if (isRoot()) {\n      for (Iterator<FltkImgbuf> it = scaledBuffers->iterator(); it.hasNext();){\n         FltkImgbuf *sb = it.getNext ();\n         sb->copiedRows->clear();\n\n         DBG_IF_RTFL {\n            lout::misc::StringBuffer sb;\n            copiedRows->intoStringBuffer (&sb);\n            DBG_OBJ_SET_SYM (\"copiedRows\", sb.getChars ());\n         }\n      }\n   }\n}\n\ncore::Imgbuf* FltkImgbuf::getScaledBuf (int width, int height)\n{\n   if (!isRoot())\n      return root->getScaledBuf (width, height);\n\n   if (width > MAX_WIDTH) {\n      // Similar to init.\n      MSG(\"FltkImgbuf::getScaledBuf: cannot handle large width %d\\n\", width);\n      return getScaledBuf (MAX_WIDTH, height);\n   }\n   if (height > MAX_HEIGHT) {\n      MSG(\"FltkImgbuf::getScaledBuf: cannot handle large height %d\\n\", height);\n      return getScaledBuf (width, MAX_HEIGHT);\n   }\n\n   if (width == this->width && height == this->height) {\n      ref ();\n      return this;\n   }\n\n   for (Iterator <FltkImgbuf> it = scaledBuffers->iterator(); it.hasNext(); ) {\n      FltkImgbuf *sb = it.getNext ();\n      if (sb->width == width && sb->height == height) {\n         sb->ref ();\n         return sb;\n      }\n   }\n\n   // Check for excessive image sizes which would cause crashes due to\n   // too big allocations for the image buffer. In this case we return\n   // a pointer to the unscaled image buffer.\n   if (excessiveImageDimensions (width, height)) {\n      MSG(\"FltkImgbuf::getScaledBuf: suspicious image size request %d x %d\\n\",\n           width, height);\n      ref ();\n      return this;\n   }\n\n   // This size is not yet used, so a new buffer has to be created.\n   FltkImgbuf *sb = new FltkImgbuf (type, width, height, gamma, this);\n   scaledBuffers->append (sb);\n   DBG_OBJ_ASSOC_CHILD (sb);\n\n   return sb;\n}\n\nvoid FltkImgbuf::getRowArea (int row, dw::core::Rectangle *area)\n{\n   // TODO: May have to be adjusted.\n\n   if (isRoot()) {\n      /* root buffer */\n      area->x = 0;\n      area->y = row;\n      area->width = width;\n      area->height = 1;\n      _MSG(\"::getRowArea: area x=%d y=%d width=%d height=%d\\n\",\n           area->x, area->y, area->width, area->height);\n   } else {\n      if (row > root->height)\n         area->x = area->y = area->width = area->height = 0;\n      else {\n         // scaled buffer\n         int sr1 = scaledY (row);\n         int sr2 = scaledY (row + 1);\n\n         area->x = 0;\n         area->y = sr1;\n         area->width = width;\n         area->height = sr2 - sr1;\n         _MSG(\"::getRowArea: area x=%d y=%d width=%d height=%d\\n\",\n              area->x, area->y, area->width, area->height);\n      }\n   }\n}\n\nint FltkImgbuf::getRootWidth ()\n{\n   return root ? root->width : width;\n}\n\nint FltkImgbuf::getRootHeight ()\n{\n   return root ? root->height : height;\n}\n\ncore::Imgbuf *FltkImgbuf::createSimilarBuf (int width, int height)\n{\n   return new FltkImgbuf (type, width, height, gamma);\n}\n\nvoid FltkImgbuf::copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot,\n                         int xSrc, int ySrc, int widthSrc, int heightSrc)\n{\n   FltkImgbuf *fDest = (FltkImgbuf*)dest;\n   assert (bpp == fDest->bpp);\n\n   int xSrc2 = lout::misc::min (xSrc + widthSrc, fDest->width - xDestRoot);\n   int ySrc2 = lout::misc::min (ySrc + heightSrc, fDest->height - yDestRoot);\n\n   //printf (\"copying from (%d, %d), %d x %d to (%d, %d) (root) => \"\n   //        \"xSrc2 = %d, ySrc2 = %d\\n\",\n   //        xSrc, ySrc, widthSrc, heightSrc, xDestRoot, yDestRoot,\n   //        xSrc2, ySrc2);\n\n   for (int x = xSrc; x < xSrc2; x++)\n      for (int y = ySrc; y < ySrc2; y++) {\n         int iSrc = x + width * y;\n         int iDest = xDestRoot + x + fDest->width * (yDestRoot + y);\n\n         //printf (\"   (%d, %d): %d -> %d\\n\", x, y, iSrc, iDest);\n\n         for (int b = 0; b < bpp; b++)\n            fDest->rawdata[bpp * iDest + b] = rawdata[bpp * iSrc + b];\n      }\n}\n\nvoid FltkImgbuf::ref ()\n{\n   refCount++;\n\n   //if (root)\n   //   MSG(\"FltkImgbuf[scaled %p, root is %p]: ref() => %d\\n\",\n   //        this, root, refCount);\n   //else\n   //   MSG(\"FltkImgbuf[root %p]: ref() => %d\\n\", this, refCount);\n}\n\nvoid FltkImgbuf::unref ()\n{\n   //if (root)\n   //   MSG(\"FltkImgbuf[scaled %p, root is %p]: ref() => %d\\n\",\n   //       this, root, refCount - 1);\n   //else\n   //   MSG(\"FltkImgbuf[root %p]: ref() => %d\\n\", this, refCount - 1);\n\n   if (--refCount == 0) {\n      if (isRoot ()) {\n         // Root buffer, it must be ensured that no scaled buffers are left.\n         // See also FltkImgbuf::detachScaledBuf().\n         if (scaledBuffers->isEmpty () && deleteOnUnref) {\n            delete this;\n         } else {\n            _MSG(\"FltkImgbuf[root %p]: not deleted. numScaled=%d\\n\",\n                 this, scaledBuffers->size ());\n         }\n      } else\n         // Scaled buffer buffer, simply delete it.\n         delete this;\n   }\n}\n\nbool FltkImgbuf::lastReference ()\n{\n   return refCount == 1 &&\n      (scaledBuffers == NULL || scaledBuffers->isEmpty ());\n}\n\nvoid FltkImgbuf::setDeleteOnUnref (bool deleteOnUnref)\n{\n   assert (isRoot ());\n   this->deleteOnUnref = deleteOnUnref;\n}\n\nbool FltkImgbuf::isReferred ()\n{\n   return refCount != 0 ||\n      (scaledBuffers != NULL && !scaledBuffers->isEmpty ());\n}\n\n\nint FltkImgbuf::scaledY(int ySrc)\n{\n   // TODO: May have to be adjusted.\n   assert (root != NULL);\n   return ySrc * height / root->height;\n}\n\nint FltkImgbuf::backscaledY(int yScaled)\n{\n   assert (root != NULL);\n\n   // Notice that rounding errors because of integers do not play a\n   // role. This method cannot be the exact inverse of scaledY, since\n   // scaleY is not bijective, and so not invertible. Instead, both\n   // values always return the smallest value.\n   return yScaled * root->height / height;\n}\n\nvoid FltkImgbuf::draw (Fl_Widget *target, int xRoot, int yRoot,\n                       int x, int y, int width, int height)\n{\n   // TODO: Clarify the question, whether \"target\" is the current widget\n   //       (and so has not to be passed at all).\n\n   _MSG(\"::draw: xRoot=%d x=%d yRoot=%d y=%d width=%d height=%d\\n\"\n        \"        this->width=%d this->height=%d\\n\",\n        xRoot, x, yRoot, y, width, height, this->width, this->height);\n\n   if (x > this->width || y > this->height) {\n      return;\n   }\n\n   if (x + width > this->width) {\n      width = this->width - x;\n   }\n\n   if (y + height > this->height) {\n      height = this->height - y;\n   }\n\n   fl_draw_image(rawdata+bpp*(y*this->width + x), xRoot + x, yRoot + y, width,\n                 height, bpp, this->width * bpp);\n\n}\n\n} // namespace fltk\n} // namespace dw\n"
  },
  {
    "path": "dw/fltkimgbuf.hh",
    "content": "#ifndef __DW_FLTKIMGBUF_HH__\n#define __DW_FLTKIMGBUF_HH__\n\n#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__\n#   error Do not include this file directly, use \"fltkcore.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace fltk {\n\nclass FltkImgbuf: public core::Imgbuf\n{\nprivate:\n   class GammaCorrectionTable: public lout::object::Object\n   {\n   public:\n      double gamma;\n      uchar map[256];\n   };\n\n   FltkImgbuf *root;\n   int refCount;\n   bool deleteOnUnref;\n   lout::container::typed::List <FltkImgbuf> *scaledBuffers;\n\n   int width, height;\n   Type type;\n   double gamma;\n\n//{\n   int bpp;\n   uchar *rawdata;\n//}\n\n   // This is just for testing drawing, it has to be replaced by\n   // the image buffer.\n   lout::misc::BitSet *copiedRows;\n\n   static lout::container::typed::Vector <GammaCorrectionTable>\n      *gammaCorrectionTables;\n\n   static uchar *findGammaCorrectionTable (double gamma);\n   static bool excessiveImageDimensions (int width, int height);\n\n   FltkImgbuf (Type type, int width, int height, double gamma,\n               FltkImgbuf *root);\n   void init (Type type, int width, int height, double gamma, FltkImgbuf *root);\n   int scaledY(int ySrc);\n   int backscaledY(int yScaled);\n   int isRoot() { return (root == NULL); }\n   void detachScaledBuf (FltkImgbuf *scaledBuf);\n\nprotected:\n   ~FltkImgbuf ();\n\npublic:\n   FltkImgbuf (Type type, int width, int height, double gamma);\n\n   static void freeall ();\n\n   void setCMap (int *colors, int num_colors);\n   inline void scaleRow (int row, const core::byte *data);\n   inline void scaleRowSimple (int row, const core::byte *data);\n   inline void scaleRowBeautiful (int row, const core::byte *data);\n   inline static void scaleBuffer (const core::byte *src, int srcWidth,\n                                   int srcHeight, core::byte *dest,\n                                   int destWidth, int destHeight, int bpp,\n                                   double gamma);\n\n   void newScan ();\n   void copyRow (int row, const core::byte *data);\n   core::Imgbuf* getScaledBuf (int width, int height);\n   void getRowArea (int row, dw::core::Rectangle *area);\n   int  getRootWidth ();\n   int  getRootHeight ();\n   core::Imgbuf *createSimilarBuf (int width, int height);\n   void copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot,\n                int xSrc, int ySrc, int widthSrc, int heightSrc);\n   void ref ();\n   void unref ();\n\n   bool lastReference ();\n   void setDeleteOnUnref (bool deleteOnUnref);\n   bool isReferred ();\n\n   void draw (Fl_Widget *target, int xRoot, int yRoot,\n              int x, int y, int width, int height);\n};\n\n} // namespace fltk\n} // namespace dw\n\n#endif // __DW_FLTK_IMGBUF_HH__\n"
  },
  {
    "path": "dw/fltkmisc.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"../lout/msg.h\"\n#include \"fltkmisc.hh\"\n\n#include <FL/Fl.H>\n#include <stdio.h>\n\nnamespace dw {\nnamespace fltk {\nnamespace misc {\n\nint screenWidth ()\n{\n   return Fl::w ();\n}\n\nint screenHeight ()\n{\n   return Fl::h ();\n}\n\nvoid warpPointer (int x, int y)\n{\n   MSG_ERR(\"no warpPointer mechanism available.\\n\");\n}\n\n} // namespace misc\n} // namespace fltk\n} // namespace dw\n"
  },
  {
    "path": "dw/fltkmisc.hh",
    "content": "#ifndef __FLTKMISC_HH__\n#define __FLTKMISC_HH__\n\nnamespace dw {\nnamespace fltk {\n\n/**\n * \\brief Miscellaneous FLTK stuff.\n */\nnamespace misc {\n\nint screenWidth ();\nint screenHeight ();\n\nvoid warpPointer (int x, int y);\n\n} // namespace misc\n} // namespace fltk\n} // namespace dw\n\n\n#endif // __FLTKMISC_HH__\n"
  },
  {
    "path": "dw/fltkplatform.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n\n#include \"../lout/msg.h\"\n#include \"../lout/debug.hh\"\n#include \"fltkcore.hh\"\n\n#include <FL/fl_draw.H>\n#include <FL/Fl_Box.H>\n#include <FL/Fl_Tooltip.H>\n#include <FL/Fl_Menu_Window.H>\n#include <FL/Fl_Paged_Device.H>\n\n/*\n * Local data\n */\n\n/* Tooltips */\nstatic Fl_Menu_Window *tt_window = NULL;\nstatic int in_tooltip = 0, req_tooltip = 0;\n\nnamespace dw {\nnamespace fltk {\n\nusing namespace lout;\n\n/**\n * \\todo Distinction between italics and oblique would be nice.\n */\n\ncontainer::typed::HashTable <dw::core::style::FontAttrs,\n                             FltkFont> *FltkFont::fontsTable =\n   new container::typed::HashTable <dw::core::style::FontAttrs,\n                                    FltkFont> (false, false);\n\ncontainer::typed::HashTable <lout::object::ConstString,\n                             FltkFont::FontFamily> *FltkFont::systemFonts =\n                             NULL;\n\nFltkFont::FontFamily FltkFont::standardFontFamily (FL_HELVETICA,\n                                                   FL_HELVETICA_BOLD,\n                                                   FL_HELVETICA_ITALIC,\n                                                   FL_HELVETICA_BOLD_ITALIC);\n\nFltkFont::FontFamily::FontFamily (Fl_Font fontNormal, Fl_Font fontBold,\n                                  Fl_Font fontItalic, Fl_Font fontBoldItalic)\n{\n   font[0] = fontNormal;\n   font[1] = fontBold;\n   font[2] = fontItalic;\n   font[3] = fontBoldItalic;\n}\n\nvoid FltkFont::FontFamily::set (Fl_Font f, int attrs)\n{\n   int idx = 0;\n   if (attrs & FL_BOLD)\n      idx += 1;\n   if (attrs & FL_ITALIC)\n      idx += 2;\n   font[idx] = f;\n}\n\nFl_Font FltkFont::FontFamily::get (int attrs)\n{\n   int idx = 0;\n   if (attrs & FL_BOLD)\n      idx += 1;\n   if (attrs & FL_ITALIC)\n      idx += 2;\n\n   // should the desired font style not exist, we\n   // return the normal font of the fontFamily\n   return font[idx] >= 0 ? font[idx] : font[0];\n}\n\n\n\nFltkFont::FltkFont (core::style::FontAttrs *attrs)\n{\n   if (!systemFonts)\n      initSystemFonts ();\n\n   copyAttrs (attrs);\n\n   int fa = 0;\n   if (weight >= 500)\n      fa |= FL_BOLD;\n   if (style != core::style::FONT_STYLE_NORMAL)\n      fa |= FL_ITALIC;\n\n   object::ConstString nameString (name);\n   FontFamily *family = systemFonts->get (&nameString);\n   if (!family)\n      family = &standardFontFamily;\n\n   font = family->get (fa);\n\n   fl_font(font, size);\n   // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in\n   // 1.3.0 (STR #2688).\n   spaceWidth = misc::max(0, (int)fl_width(\" \") + letterSpacing);\n   int xx, xy, xw, xh;\n   fl_text_extents(\"x\", xx, xy, xw, xh);\n   xHeight = xh;\n   descent = fl_descent();\n   ascent = fl_height() - descent;\n}\n\nFltkFont::~FltkFont ()\n{\n   fontsTable->remove (this);\n}\n\nstatic void strstrip(char *big, const char *little)\n{\n   if (strlen(big) >= strlen(little) &&\n      misc::AsciiStrcasecmp(big + strlen(big) - strlen(little), little) == 0)\n      *(big + strlen(big) - strlen(little)) = '\\0';\n}\n\nvoid FltkFont::initSystemFonts ()\n{\n   systemFonts = new container::typed::HashTable\n      <lout::object::ConstString, FontFamily> (true, true);\n\n   int k = Fl::set_fonts (\"-*-iso10646-1\");\n   for (int i = 0; i < k; i++) {\n      int t;\n      char *name = strdup (Fl::get_font_name ((Fl_Font) i, &t));\n\n      // normalize font family names (strip off \"bold\", \"italic\")\n      if (t & FL_ITALIC)\n         strstrip(name, \" italic\");\n      if (t & FL_BOLD)\n         strstrip(name, \" bold\");\n\n      _MSG(\"Found font: %s%s%s\\n\", name, t & FL_BOLD ? \" bold\" : \"\",\n                                  t & FL_ITALIC ? \" italic\" : \"\");\n\n      object::String *familyName = new object::String(name);\n      free (name);\n      FontFamily *family = systemFonts->get (familyName);\n\n      if (family) {\n         family->set ((Fl_Font) i, t);\n         delete familyName;\n      } else {\n         // set first font of family also as normal font in case there\n         // is no normal (non-bold, non-italic) font\n         family = new FontFamily ((Fl_Font) i, -1, -1, -1);\n         family->set ((Fl_Font) i, t);\n         systemFonts->put (familyName, family);\n      }\n   }\n}\n\nbool\nFltkFont::fontExists (const char *name)\n{\n   if (!systemFonts)\n      initSystemFonts ();\n   object::ConstString familyName (name);\n   return systemFonts->get (&familyName) != NULL;\n}\n\nFl_Font\nFltkFont::get (const char *name, int attrs)\n{\n   if (!systemFonts)\n      initSystemFonts ();\n   object::ConstString familyName (name);\n   FontFamily *family = systemFonts->get (&familyName);\n   if (family)\n      return family->get (attrs);\n   else\n      return FL_HELVETICA;\n}\n\nbool\nFltkPlatform::fontExists (const char *name)\n{\n   return FltkFont::fontExists (name);\n}\n\nFltkFont*\nFltkFont::create (core::style::FontAttrs *attrs)\n{\n   FltkFont *font = fontsTable->get (attrs);\n\n   if (font == NULL) {\n      font = new FltkFont (attrs);\n      fontsTable->put (font, font);\n   }\n\n   return font;\n}\n\ncontainer::typed::HashTable <dw::core::style::ColorAttrs,\n                             FltkColor>\n   *FltkColor::colorsTable =\n      new container::typed::HashTable <dw::core::style::ColorAttrs,\n                                       FltkColor> (false, false);\n\nFltkColor::FltkColor (int color): Color (color)\n{\n   this->color = color;\n\n   if (!(colors[SHADING_NORMAL] = shadeColor (color, SHADING_NORMAL) << 8))\n      colors[SHADING_NORMAL] = FL_BLACK;\n   if (!(colors[SHADING_INVERSE] = shadeColor (color, SHADING_INVERSE) << 8))\n      colors[SHADING_INVERSE] = FL_BLACK;\n   if (!(colors[SHADING_DARK] = shadeColor (color, SHADING_DARK) << 8))\n      colors[SHADING_DARK] = FL_BLACK;\n   if (!(colors[SHADING_LIGHT] = shadeColor (color, SHADING_LIGHT) << 8))\n      colors[SHADING_LIGHT] = FL_BLACK;\n}\n\nFltkColor::~FltkColor ()\n{\n   colorsTable->remove (this);\n}\n\nFltkColor * FltkColor::create (int col)\n{\n   ColorAttrs attrs(col);\n   FltkColor *color = colorsTable->get (&attrs);\n\n   if (color == NULL) {\n      color = new FltkColor (col);\n      colorsTable->put (color, color);\n   }\n\n   return color;\n}\n\nFltkTooltip::FltkTooltip (const char *text) : Tooltip(text)\n{\n}\n\nFltkTooltip::~FltkTooltip ()\n{\n   if (in_tooltip || req_tooltip)\n      cancel(); /* cancel tooltip window */\n}\n\nFltkTooltip *FltkTooltip::create (const char *text)\n{\n   return new FltkTooltip(text);\n}\n\n/*\n * Tooltip callback: used to delay it a bit\n * INVARIANT: Only one instance of this function is requested.\n */\nstatic void tooltip_tcb(void *data)\n{\n   req_tooltip = 2;\n   ((FltkTooltip *)data)->onEnter();\n   req_tooltip = 0;\n}\n\nvoid FltkTooltip::onEnter()\n{\n   _MSG(\"FltkTooltip::onEnter\\n\");\n   if (!str || !*str)\n      return;\n   if (req_tooltip == 0) {\n      Fl::remove_timeout(tooltip_tcb);\n      Fl::add_timeout(1.0, tooltip_tcb, this);\n      req_tooltip = 1;\n      return;\n   }\n\n   if (!tt_window) {\n      tt_window = new Fl_Menu_Window(0,0,100,24);\n      tt_window->set_override();\n      tt_window->box(FL_NO_BOX);\n      Fl_Box *b = new Fl_Box(0,0,100,24);\n      b->box(FL_BORDER_BOX);\n      b->color(fl_color_cube(FL_NUM_RED-1, FL_NUM_GREEN-1, FL_NUM_BLUE-2));\n      b->labelcolor(FL_BLACK);\n      b->labelfont(FL_HELVETICA);\n      b->labelsize(14);\n      b->align(FL_ALIGN_WRAP|FL_ALIGN_LEFT|FL_ALIGN_INSIDE);\n      tt_window->resizable(b);\n      tt_window->end();\n   }\n\n   /* prepare tooltip window */\n   int x, y;\n   Fl_Box *box = (Fl_Box*)tt_window->child(0);\n   box->label(str);\n   Fl::get_mouse(x,y); y += 6;\n   /* calculate window size */\n   int ww, hh;\n   ww = 800; // max width;\n   box->measure_label(ww, hh);\n   ww += 6 + 2 * Fl::box_dx(box->box());\n   hh += 6 + 2 * Fl::box_dy(box->box());\n   tt_window->resize(x,y,ww,hh);\n   tt_window->show();\n   in_tooltip = 1;\n}\n\n/*\n * Leaving the widget cancels the tooltip\n */\nvoid FltkTooltip::onLeave()\n{\n   _MSG(\" FltkTooltip::onLeave  in_tooltip=%d\\n\", in_tooltip);\n   cancel();\n}\n\nvoid FltkPlatform::cancelTooltip()\n{\n   FltkTooltip::cancel();\n}\n\n/*\n * Remove a shown tooltip or cancel a pending one\n */\nvoid FltkTooltip::cancel()\n{\n   if (req_tooltip) {\n      Fl::remove_timeout(tooltip_tcb);\n      req_tooltip = 0;\n   }\n   if (!in_tooltip) return;\n   in_tooltip = 0;\n   tt_window->hide();\n\n   /* WORKAROUND: (Black magic here)\n    * Hiding a tooltip with the keyboard or mousewheel doesn't work.\n    * The code below \"fixes\" the problem */\n   Fl_Widget *widget = Fl::belowmouse();\n   if (widget && widget->window()) {\n      widget->window()->damage(FL_DAMAGE_EXPOSE,0,0,1,1);\n   }\n}\n\nvoid FltkTooltip::onMotion()\n{\n}\n\nvoid FltkView::addFltkWidget (Fl_Widget *widget,\n                              core::Allocation *allocation)\n{\n}\n\nvoid FltkView::removeFltkWidget (Fl_Widget *widget)\n{\n}\n\nvoid FltkView::allocateFltkWidget (Fl_Widget *widget,\n                                   core::Allocation *allocation)\n{\n}\n\nvoid FltkView::drawFltkWidget (Fl_Widget *widget, core::Rectangle *area)\n{\n}\n\n\ncore::ui::LabelButtonResource *\nFltkPlatform::FltkResourceFactory::createLabelButtonResource (const char\n                                                              *label)\n{\n   return new ui::FltkLabelButtonResource (platform, label);\n}\n\ncore::ui::ComplexButtonResource *\nFltkPlatform::FltkResourceFactory::createComplexButtonResource (core::Widget\n                                                                *widget,\n                                                                bool relief)\n{\n   return new ui::FltkComplexButtonResource (platform, widget, relief);\n}\n\ncore::ui::ListResource *\nFltkPlatform::FltkResourceFactory::createListResource (core::ui\n                                                       ::ListResource\n                                                       ::SelectionMode\n                                                       selectionMode, int rows)\n{\n   return new ui::FltkListResource (platform, selectionMode, rows);\n}\n\ncore::ui::OptionMenuResource *\nFltkPlatform::FltkResourceFactory::createOptionMenuResource ()\n{\n   return new ui::FltkOptionMenuResource (platform);\n}\n\ncore::ui::EntryResource *\nFltkPlatform::FltkResourceFactory::createEntryResource (int size,\n                                                        bool password,\n                                                        const char *label,\n                                                       const char *placeholder)\n{\n   return new ui::FltkEntryResource (platform, size, password, label,\n                                     placeholder);\n}\n\ncore::ui::MultiLineTextResource *\nFltkPlatform::FltkResourceFactory::createMultiLineTextResource (int cols,\n                                                                int rows,\n                                                       const char *placeholder)\n{\n   return new ui::FltkMultiLineTextResource (platform, cols, rows,placeholder);\n}\n\ncore::ui::CheckButtonResource *\nFltkPlatform::FltkResourceFactory::createCheckButtonResource (bool activated)\n{\n   return new ui::FltkCheckButtonResource (platform, activated);\n}\n\ncore::ui::RadioButtonResource\n*FltkPlatform::FltkResourceFactory::createRadioButtonResource\n(core::ui::RadioButtonResource *groupedWith, bool activated)\n{\n   return\n      new ui::FltkRadioButtonResource (platform,\n                                       (ui::FltkRadioButtonResource*)\n                                       groupedWith,\n                                       activated);\n}\n\n// ----------------------------------------------------------------------\n\nFltkPlatform::FltkPlatform ()\n{\n   DBG_OBJ_CREATE (\"dw::fltk::FltkPlatform\");\n\n   layout = NULL;\n   idleQueue = new container::typed::List <IdleFunc> (true);\n   idleFuncRunning = false;\n   idleFuncId = 0;\n\n   view = NULL;\n   resources = new container::typed::List <ui::FltkResource> (false);\n\n   resourceFactory.setPlatform (this);\n}\n\nFltkPlatform::~FltkPlatform ()\n{\n   if (idleFuncRunning)\n      Fl::remove_idle (generalStaticIdle, (void*)this);\n   delete idleQueue;\n   delete resources;\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid FltkPlatform::setLayout (core::Layout *layout)\n{\n   this->layout = layout;\n   DBG_OBJ_ASSOC_CHILD (layout);\n}\n\n\nvoid FltkPlatform::attachView (core::View *view)\n{\n   if (this->view)\n      MSG_ERR(\"FltkPlatform::attachView: multiple views!\\n\");\n   this->view = (FltkView*)view;\n\n   for (container::typed::Iterator <ui::FltkResource> it =\n           resources->iterator (); it.hasNext (); ) {\n      ui::FltkResource *resource = it.getNext ();\n      resource->attachView (this->view);\n   }\n}\n\n\nvoid FltkPlatform::detachView (core::View *view)\n{\n   if (this->view != view)\n      MSG_ERR(\"FltkPlatform::detachView: this->view: %p view: %p\\n\",\n              this->view, view);\n\n   for (container::typed::Iterator <ui::FltkResource> it =\n           resources->iterator (); it.hasNext (); ) {\n      ui::FltkResource *resource = it.getNext ();\n      resource->detachView ((FltkView*)view);\n   }\n   this->view = NULL;\n}\n\n\nint FltkPlatform::textWidth (core::style::Font *font, const char *text,\n                             int len)\n{\n   char chbuf[4];\n   int c, cu;\n   int width = 0;\n   FltkFont *ff = (FltkFont*) font;\n   int curr = 0, next = 0, nb;\n\n   if (font->fontVariant == core::style::FONT_VARIANT_SMALL_CAPS) {\n      int sc_fontsize = lout::misc::roundInt(ff->size * 0.78);\n      for (curr = 0; next < len; curr = next) {\n         next = nextGlyph(text, curr);\n         c = fl_utf8decode(text + curr, text + next, &nb);\n         if ((cu = fl_toupper(c)) == c) {\n            /* already uppercase, just draw the character */\n            fl_font(ff->font, ff->size);\n            if (fl_nonspacing(cu) == 0) {\n               width += font->letterSpacing;\n               width += (int)fl_width(text + curr, next - curr);\n            }\n         } else {\n            /* make utf8 string for converted char */\n            nb = fl_utf8encode(cu, chbuf);\n            fl_font(ff->font, sc_fontsize);\n            if (fl_nonspacing(cu) == 0) {\n               width += font->letterSpacing;\n               width += (int)fl_width(chbuf, nb);\n            }\n         }\n      }\n   } else {\n      fl_font (ff->font, ff->size);\n      width = (int) fl_width (text, len);\n\n      if (font->letterSpacing) {\n         int curr = 0, next = 0;\n\n         while (next < len) {\n            next = nextGlyph(text, curr);\n            c = fl_utf8decode(text + curr, text + next, &nb);\n            if (fl_nonspacing(c) == 0)\n               width += font->letterSpacing;\n            curr = next;\n         }\n      }\n   }\n\n   return width;\n}\n\nchar *FltkPlatform::textToUpper (const char *text, int len)\n{\n   char *newstr = NULL;\n\n   if (len > 0) {\n      int newlen;\n\n      newstr = (char*) malloc(3 * len + 1);\n      newlen = fl_utf_toupper((const unsigned char*)text, len, newstr);\n      assert(newlen <= 3 * len);\n      newstr[newlen] = '\\0';\n   }\n   return newstr;\n}\n\nchar *FltkPlatform::textToLower (const char *text, int len)\n{\n   char *newstr = NULL;\n\n   if (len > 0) {\n      int newlen;\n\n      newstr = (char*) malloc(3 * len + 1);\n      newlen = fl_utf_tolower((const unsigned char*)text, len, newstr);\n      assert(newlen <= 3 * len);\n      newstr[newlen] = '\\0';\n   }\n   return newstr;\n}\n\nint FltkPlatform::nextGlyph (const char *text, int idx)\n{\n   return fl_utf8fwd (&text[idx + 1], text, &text[strlen (text)]) - text;\n}\n\nint FltkPlatform::prevGlyph (const char *text, int idx)\n{\n   return fl_utf8back (&text[idx - 1], text, &text[strlen (text)]) - text;\n}\n\nfloat FltkPlatform::dpiX ()\n{\n   float horizontal, vertical;\n\n   Fl::screen_dpi(horizontal, vertical);\n   return horizontal;\n}\n\nfloat FltkPlatform::dpiY ()\n{\n   float horizontal, vertical;\n\n   Fl::screen_dpi(horizontal, vertical);\n   return vertical;\n}\n\nvoid FltkPlatform::generalStaticIdle (void *data)\n{\n   ((FltkPlatform*)data)->generalIdle();\n}\n\nvoid FltkPlatform::generalIdle ()\n{\n   IdleFunc *idleFunc;\n\n   if (!idleQueue->isEmpty ()) {\n      /* Execute the first function in the list. */\n      idleFunc = idleQueue->getFirst ();\n      (layout->*(idleFunc->func)) ();\n\n      /* Remove this function. */\n      idleQueue->removeRef(idleFunc);\n   }\n\n   if (idleQueue->isEmpty()) {\n      idleFuncRunning = false;\n      Fl::remove_idle (generalStaticIdle, (void*)this);\n   }\n}\n\n/**\n * \\todo Incomplete comments.\n */\nint FltkPlatform::addIdle (void (core::Layout::*func) ())\n{\n   /*\n    * Since ... (todo) we have to wrap around fltk_add_idle. There is only one\n    * idle function, the passed idle function is put into a queue.\n    */\n   if (!idleFuncRunning) {\n      Fl::add_idle (generalStaticIdle, (void*)this);\n      idleFuncRunning = true;\n   }\n\n   idleFuncId++;\n\n   IdleFunc *idleFunc = new IdleFunc();\n   idleFunc->id = idleFuncId;\n   idleFunc->func = func;\n   idleQueue->append (idleFunc);\n\n   return idleFuncId;\n}\n\nvoid FltkPlatform::removeIdle (int idleId)\n{\n   bool found;\n   container::typed::Iterator <IdleFunc> it;\n   IdleFunc *idleFunc;\n\n   for (found = false, it = idleQueue->iterator(); !found && it.hasNext(); ) {\n      idleFunc = it.getNext();\n      if (idleFunc->id == idleId) {\n         idleQueue->removeRef (idleFunc);\n         found = true;\n      }\n   }\n\n   if (idleFuncRunning && idleQueue->isEmpty())\n      Fl::remove_idle (generalStaticIdle, (void*)this);\n}\n\ncore::style::Font *FltkPlatform::createFont (core::style::FontAttrs\n                                             *attrs,\n                                             bool tryEverything)\n{\n   return FltkFont::create (attrs);\n}\n\ncore::style::Color *FltkPlatform::createColor (int color)\n{\n   return FltkColor::create (color);\n}\n\ncore::style::Tooltip *FltkPlatform::createTooltip (const char *text)\n{\n   return FltkTooltip::create (text);\n}\n\nvoid FltkPlatform::copySelection(const char *text)\n{\n   Fl::copy(text, strlen(text), 0);\n}\n\ncore::Imgbuf *FltkPlatform::createImgbuf (core::Imgbuf::Type type,\n                                          int width, int height, double gamma)\n{\n   return new FltkImgbuf (type, width, height, gamma);\n}\n\ncore::ui::ResourceFactory *FltkPlatform::getResourceFactory ()\n{\n   return &resourceFactory;\n}\n\n\nvoid FltkPlatform::attachResource (ui::FltkResource *resource)\n{\n   resources->append (resource);\n   resource->attachView (view);\n}\n\nvoid FltkPlatform::detachResource (ui::FltkResource *resource)\n{\n   resources->removeRef (resource);\n}\n\n} // namespace fltk\n} // namespace dw\n"
  },
  {
    "path": "dw/fltkplatform.hh",
    "content": "#ifndef __DW_FLTKPLATFORM_HH__\n#define __DW_FLTKPLATFORM_HH__\n\n#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__\n#   error Do not include this file directly, use \"fltkcore.hh\" instead.\n#endif\n\nnamespace dw {\n\n/**\n * \\brief This namespace contains FLTK implementations of Dw interfaces.\n */\nnamespace fltk {\n\nclass FltkFont: public core::style::Font\n{\n   class FontFamily: public lout::object::Object {\n         Fl_Font font[4];\n      public:\n         FontFamily (Fl_Font fontNormal, Fl_Font fontBold,\n                     Fl_Font fontItalic, Fl_Font fontBoldItalic);\n         void set (Fl_Font, int attrs);\n         Fl_Font get (int attrs);\n   };\n\n   static FontFamily standardFontFamily;\n\n   static lout::container::typed::HashTable <lout::object::ConstString,\n                                             FontFamily> *systemFonts;\n   static lout::container::typed::HashTable <dw::core::style::FontAttrs,\n                                       FltkFont> *fontsTable;\n\n   FltkFont (core::style::FontAttrs *attrs);\n   ~FltkFont ();\n\n   static void initSystemFonts ();\n\npublic:\n   Fl_Font font;\n\n   static FltkFont *create (core::style::FontAttrs *attrs);\n   static bool fontExists (const char *name);\n   static Fl_Font get (const char *name, int attrs);\n};\n\n\nclass FltkColor: public core::style::Color\n{\n   static lout::container::typed::HashTable <dw::core::style::ColorAttrs,\n                                       FltkColor> *colorsTable;\n\n   FltkColor (int color);\n   ~FltkColor ();\n\npublic:\n   int colors[SHADING_NUM];\n\n   static FltkColor *create(int color);\n};\n\nclass FltkTooltip: public core::style::Tooltip\n{\nprivate:\n   FltkTooltip (const char *text);\n   ~FltkTooltip ();\npublic:\n   static FltkTooltip *create(const char *text);\n   static void cancel();\n   void onEnter();\n   void onLeave();\n   void onMotion();\n};\n\n\n/**\n * \\brief This interface adds some more methods for all flkt-based views.\n */\nclass FltkView: public core::View\n{\npublic:\n   virtual bool usesFltkWidgets () = 0;\n\n   virtual void addFltkWidget (Fl_Widget *widget,\n                               core::Allocation *allocation);\n   virtual void removeFltkWidget (Fl_Widget *widget);\n   virtual void allocateFltkWidget (Fl_Widget *widget,\n                                    core::Allocation *allocation);\n   virtual void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area);\n};\n\n\nclass FltkPlatform: public core::Platform\n{\nprivate:\n   class FltkResourceFactory: public core::ui::ResourceFactory\n   {\n   private:\n      FltkPlatform *platform;\n\n   public:\n      inline void setPlatform (FltkPlatform *platform) {\n         this->platform = platform; }\n\n      core::ui::LabelButtonResource *createLabelButtonResource (const char\n                                                                *label);\n      core::ui::ComplexButtonResource *\n      createComplexButtonResource (core::Widget *widget, bool relief);\n      core::ui::ListResource *\n      createListResource (core::ui::ListResource::SelectionMode selectionMode,\n                          int rows);\n      core::ui::OptionMenuResource *createOptionMenuResource ();\n      core::ui::EntryResource *createEntryResource (int size, bool password,\n                                                    const char *label,\n                                                    const char *placeholder);\n      core::ui::MultiLineTextResource *createMultiLineTextResource (int cols,\n                                                                    int rows,\n                                                      const char *placeholder);\n      core::ui::CheckButtonResource *createCheckButtonResource (bool\n                                                                activated);\n      core::ui::RadioButtonResource *\n      createRadioButtonResource (core::ui::RadioButtonResource\n                                  *groupedWith, bool activated);\n   };\n\n   FltkResourceFactory resourceFactory;\n\n   class IdleFunc: public lout::object::Object\n   {\n   public:\n      int id;\n      void (core::Layout::*func) ();\n   };\n\n   core::Layout *layout;\n\n   lout::container::typed::List <IdleFunc> *idleQueue;\n   bool idleFuncRunning;\n   int idleFuncId;\n\n   static void generalStaticIdle(void *data);\n   void generalIdle();\n\n   FltkView *view;\n   lout::container::typed::List <ui::FltkResource> *resources;\n\npublic:\n   FltkPlatform ();\n   ~FltkPlatform ();\n\n   void setLayout (core::Layout *layout);\n\n   void attachView (core::View *view);\n\n   void detachView (core::View *view);\n\n   int textWidth (core::style::Font *font, const char *text, int len);\n   char *textToUpper (const char *text, int len);\n   char *textToLower (const char *text, int len);\n   int nextGlyph (const char *text, int idx);\n   int prevGlyph (const char *text, int idx);\n   float dpiX ();\n   float dpiY ();\n\n   int addIdle (void (core::Layout::*func) ());\n   void removeIdle (int idleId);\n\n   core::style::Font *createFont (core::style::FontAttrs *attrs,\n                                      bool tryEverything);\n   bool fontExists (const char *name);\n   core::style::Color *createColor (int color);\n   core::style::Tooltip *createTooltip (const char *text);\n   void cancelTooltip();\n\n   core::Imgbuf *createImgbuf (core::Imgbuf::Type type, int width, int height,\n                               double gamma);\n\n   void copySelection(const char *text);\n\n   core::ui::ResourceFactory *getResourceFactory ();\n\n   void attachResource (ui::FltkResource *resource);\n   void detachResource (ui::FltkResource *resource);\n};\n\n} // namespace fltk\n} // namespace dw\n\n#endif // __DW_FLTKPLATFORM_HH__\n"
  },
  {
    "path": "dw/fltkpreview.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../lout/msg.h\"\n\n#include \"fltkpreview.hh\"\n#include \"fltkmisc.hh\"\n\n#include <FL/Fl.H>\n#include <FL/Fl_Bitmap.H>\n#include <FL/fl_draw.H>\n#include <stdio.h>\n\n#include \"preview.xbm\"\n\nnamespace dw {\nnamespace fltk {\n\nFltkPreview::FltkPreview (int x, int y, int w, int h,\n                          dw::core::Layout *layout, const char *label):\n   FltkViewBase (x, y, w, h, label)\n{\n   layout->attachView (this);\n\n   scrollX = 0;\n   scrollY = 0;\n   scrollWidth = 1;\n   scrollHeight = 1;\n}\n\nFltkPreview::~FltkPreview ()\n{\n}\n\nint FltkPreview::handle (int event)\n{\n   return FltkViewBase::handle (event);\n}\n\nint FltkPreview::translateViewXToCanvasX (int x)\n{\n   return x * canvasWidth / w ();\n}\n\nint FltkPreview::translateViewYToCanvasY (int y)\n{\n   return y * canvasHeight / h ();\n}\n\nint FltkPreview::translateCanvasXToViewX (int x)\n{\n   return x * w () / canvasWidth;\n}\n\nint FltkPreview::translateCanvasYToViewY (int y)\n{\n   return y * h () / canvasHeight;\n}\n\nvoid FltkPreview::setCanvasSize (int width, int ascent, int descent)\n{\n   FltkViewBase::setCanvasSize (width, ascent, descent);\n   if (parent() && parent()->visible ())\n      ((FltkPreviewWindow*)parent())->reallocate ();\n}\n\nbool FltkPreview::usesViewport ()\n{\n   return true;\n}\n\nint FltkPreview::getHScrollbarThickness ()\n{\n   return 0;\n}\n\nint FltkPreview::getVScrollbarThickness ()\n{\n   return 0;\n}\n\nvoid FltkPreview::scrollTo (int x, int y)\n{\n   scrollX = x;\n   scrollY = y;\n}\n\nvoid FltkPreview::scroll (dw::core::ScrollCommand cmd)\n{\n   MSG_ERR(\"FltkPreview::scroll not implemented\\n\");\n}\n\nvoid FltkPreview::setViewportSize (int width, int height,\n                                   int hScrollbarThickness,\n                                   int vScrollbarThickness)\n{\n   scrollWidth = width - vScrollbarThickness;\n   scrollHeight = height - hScrollbarThickness;\n}\n\nvoid FltkPreview::drawText (core::style::Font *font,\n                            core::style::Color *color,\n                            core::style::Color::Shading shading,\n                            int x, int y, const char *text, int len)\n{\n   /*\n    * We must call setfont() before calling getwidth() (or anything\n    * else that measures text).\n    */\n   FltkFont *ff = (FltkFont*)font;\n   Fl::set_font(ff->font, translateCanvasXToViewX (ff->size));\n#if 0\n   /**\n    * \\todo Normally, this should already be known, maybe it\n    * should be passed?\n    */\n   int width = (int)getwidth (text, len);\n   int height = font->ascent; // No descent, this would look to \"bold\".\n\n   int x1 = translateCanvasXToViewX (x);\n   int y1 = translateCanvasYToViewY (y);\n   int x2 = translateCanvasXToViewX (x + width);\n   int y2 = translateCanvasYToViewY (y + height);\n   Rectangle rect (x1, y1, x2 - x1, y2 - y1);\n\n   setcolor(((FltkColor*)color)->colors[shading]);\n   fillrect (rect);\n#endif\n   fl_color(((FltkColor*)color)->colors[shading]);\n   fl_draw(text, len, translateCanvasXToViewX (x), translateCanvasYToViewY(y));\n}\n\nvoid FltkPreview::drawSimpleWrappedText (core::style::Font *font,\n                                         core::style::Color *color,\n                                         core::style::Color::Shading shading,\n                                         int x, int y, int w, int h,\n                                         const char *text)\n{\n}\n\nvoid FltkPreview::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,\n                int x, int y, int width, int height)\n{\n}\n\nbool FltkPreview::usesFltkWidgets ()\n{\n   return false;\n}\n\nvoid FltkPreview::drawFltkWidget (Fl_Widget *widget,\n                                  core::Rectangle *area)\n{\n}\n\n// ----------------------------------------------------------------------\n\nFltkPreviewWindow::FltkPreviewWindow (dw::core::Layout *layout):\n   Fl_Menu_Window (1, 1)\n{\n   box (FL_EMBOSSED_BOX);\n\n   begin ();\n   preview = new FltkPreview (BORDER_WIDTH, BORDER_WIDTH, 1, 1, layout);\n   end ();\n\n   hide ();\n}\n\nFltkPreviewWindow::~FltkPreviewWindow ()\n{\n}\n\nvoid FltkPreviewWindow::showWindow ()\n{\n   reallocate ();\n   show ();\n}\n\nvoid FltkPreviewWindow::reallocate ()\n{\n   int maxWidth = misc::screenWidth () / 2;\n   int maxHeight = misc::screenHeight () * 4 / 5;\n   int mx, my, width, height;\n   bool warp = false;\n\n   if (preview->canvasHeight * maxWidth > maxHeight * preview->canvasWidth) {\n      // Expand to maximal height (most likely case).\n      width = preview->canvasWidth * maxHeight / preview->canvasHeight;\n      height = maxHeight;\n   } else {\n      // Expand to maximal width.\n      width = maxWidth;\n      height = preview->canvasHeight * maxWidth / preview->canvasWidth;\n   }\n\n   Fl::get_mouse(mx, my);\n\n   posX = mx - preview->translateCanvasXToViewX (preview->scrollX\n                                                 + preview->scrollWidth / 2);\n   posY = my - preview->translateCanvasYToViewY (preview->scrollY\n                                                 + preview->scrollHeight / 2);\n\n   if (posX < 0) {\n      mx -= posX;\n      posX = 0;\n      warp = true;\n   } else if (posX + width > misc::screenWidth ()) {\n      mx -= (posX - (misc::screenWidth () - width));\n      posX = misc::screenWidth () - width;\n      warp = true;\n   }\n\n   if (posY < 0) {\n      my -= posY;\n      posY = 0;\n      warp = true;\n   } else if (posY + height > misc::screenHeight ()) {\n      my -= (posY - (misc::screenHeight () - height));\n      posY = misc::screenHeight () - height;\n      warp = true;\n   }\n\n   if (warp)\n      misc::warpPointer (mx, my);\n\n   resize (posX, posY, width, height);\n\n   preview->size(w () - 2 * BORDER_WIDTH, h () - 2 * BORDER_WIDTH);\n}\n\nvoid FltkPreviewWindow::hideWindow ()\n{\n   Fl_Window::hide ();\n}\n\nvoid FltkPreviewWindow::scrollTo (int mouseX, int mouseY)\n{\n   preview->scrollX =\n      preview->translateViewXToCanvasX (mouseX - posX - BORDER_WIDTH)\n      - preview->scrollWidth / 2;\n   preview->scrollY =\n      preview->translateViewYToCanvasY (mouseY - posY - BORDER_WIDTH)\n      - preview->scrollHeight / 2;\n   preview->theLayout->scrollPosChanged (preview,\n                                         preview->scrollX, preview->scrollY);\n}\n\n// ----------------------------------------------------------------------\n\nFltkPreviewButton::FltkPreviewButton (int x, int y, int w, int h,\n                                      dw::core::Layout *layout,\n                                      const char *label):\n   Fl_Button (x, y, w, h, label)\n{\n   image (new Fl_Bitmap (preview_bits, preview_width, preview_height));\n   window = new FltkPreviewWindow (layout);\n}\n\nFltkPreviewButton::~FltkPreviewButton ()\n{\n}\n\nint FltkPreviewButton::handle (int event)\n{\n   /** \\bug Some parts are missing. */\n\n   switch (event) {\n   case FL_PUSH:\n      window->showWindow ();\n      return Fl_Button::handle (event);\n\n   case FL_DRAG:\n      if (window->visible ()) {\n         window->scrollTo (Fl::event_x_root (), Fl::event_y_root ());\n         return 1;\n      }\n      return Fl_Button::handle (event);\n\n   case FL_RELEASE:\n      window->hideWindow ();\n      return Fl_Button::handle (event);\n\n   default:\n      return Fl_Button::handle (event);\n   }\n}\n\n} // namespace fltk\n} // namespace dw\n"
  },
  {
    "path": "dw/fltkpreview.hh",
    "content": "#ifndef __FlTKPREVIEW_HH__\n#define __FlTKPREVIEW_HH__\n\n#include <FL/Fl_Button.H>\n#include <FL/Fl_Menu_Window.H>\n#include \"fltkviewbase.hh\"\n\nnamespace dw {\nnamespace fltk {\n\nclass FltkPreview: public FltkViewBase\n{\n   friend class FltkPreviewWindow;\n\nprivate:\n   int scrollX, scrollY, scrollWidth, scrollHeight;\n\nprotected:\n   int translateViewXToCanvasX (int x);\n   int translateViewYToCanvasY (int y);\n   int translateCanvasXToViewX (int x);\n   int translateCanvasYToViewY (int y);\n\npublic:\n   FltkPreview (int x, int y, int w, int h, dw::core::Layout *layout,\n                const char *label = 0);\n   ~FltkPreview ();\n\n   int handle (int event);\n\n   void setCanvasSize (int width, int ascent, int descent);\n\n   bool usesViewport ();\n   int getHScrollbarThickness ();\n   int getVScrollbarThickness ();\n   void scrollTo (int x, int y);\n   void scroll (dw::core::ScrollCommand cmd);\n   void setViewportSize (int width, int height,\n                         int hScrollbarThickness, int vScrollbarThickness);\n\n   void drawText (core::style::Font *font,\n                  core::style::Color *color,\n                  core::style::Color::Shading shading,\n                  int x, int y, const char *text, int len);\n   void drawSimpleWrappedText (core::style::Font *font,\n                               core::style::Color *color,\n                               core::style::Color::Shading shading,\n                               int x, int y, int w, int h,\n                               const char *text);\n   void drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,\n                   int x, int y, int width, int height);\n\n   bool usesFltkWidgets ();\n   void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area);\n};\n\n\nclass FltkPreviewWindow: public Fl_Menu_Window\n{\nprivate:\n   enum { BORDER_WIDTH = 2 };\n\n   FltkPreview *preview;\n   int posX, posY;\n\npublic:\n   FltkPreviewWindow (dw::core::Layout *layout);\n   ~FltkPreviewWindow ();\n\n   void reallocate ();\n\n   void showWindow ();\n   void hideWindow ();\n\n   void scrollTo (int mouseX, int mouseY);\n};\n\n\nclass FltkPreviewButton: public Fl_Button\n{\nprivate:\n   FltkPreviewWindow *window;\n\npublic:\n   FltkPreviewButton (int x, int y, int w, int h,\n                      dw::core::Layout *layout, const char *label = 0);\n   ~FltkPreviewButton ();\n\n   int handle (int event);\n};\n\n} // namespace fltk\n} // namespace dw\n\n#endif // __FlTKPREVIEW_HH__\n"
  },
  {
    "path": "dw/fltkui.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"fltkcore.hh\"\n#include \"fltkflatview.hh\"\n#include \"fltkcomplexbutton.hh\"\n#include \"../lout/msg.h\"\n#include \"../lout/misc.hh\"\n\n#include <FL/Fl.H>\n#include <FL/fl_draw.H>\n#include <FL/Fl_Input.H>\n#include <FL/Fl_Text_Editor.H>\n#include <FL/Fl_Check_Button.H>\n#include <FL/Fl_Round_Button.H>\n#include <FL/Fl_Choice.H>\n#include <FL/Fl_Browser.H>\n\n#include <stdio.h>\n\n//----------------------------------------------------------------------------\n\nstatic Fl_Color fltkui_dimmed(Fl_Color c, Fl_Color bg)\n{\n   return fl_color_average(c, bg, .33f);\n};\n\n//----------------------------------------------------------------------------\n/*\n * Local sub classes\n */\n\n/*\n * Used to show optional placeholder text and to enable CTRL+{a,e,d,k} in\n * form inputs (for start,end,del,cut)\n */\nclass CustInput2 : public Fl_Input {\npublic:\n   CustInput2 (int x, int y, int w, int h, const char* l=0);\n   ~CustInput2 () { if (placeholder) free(placeholder); };\n   void set_placeholder(const char *str);\n   int show_placeholder();\n   int show_normal(const char *str);\n   void textcolor(Fl_Color c);\n   void input_type(int t);\n   int value(const char* str);\n   const char* value();\n   int handle(int e);\nprivate:\n   char *placeholder;\n   bool showing_placeholder;\n   Fl_Color usual_color;\n   int usual_type;\n};\n\nCustInput2::CustInput2 (int x, int y, int w, int h, const char* l) :\n   Fl_Input(x,y,w,h,l)\n{\n   placeholder = NULL;\n   showing_placeholder = false;\n   usual_color = FL_BLACK;      /* just init until widget style is set */\n};\n\n/*\n * Show normal text.\n */\nint CustInput2::show_normal(const char *str)\n{\n   showing_placeholder = false;\n   Fl_Input::textcolor(usual_color);\n   Fl_Input::input_type(usual_type);\n   return Fl_Input::value(str);\n}\n\n/*\n * Show the placeholder text.\n */\nint CustInput2::show_placeholder()\n{\n   int ret;\n\n   showing_placeholder = true;\n   Fl_Input::textcolor(fltkui_dimmed(usual_color, color()));\n   Fl_Input::input_type(FL_NORMAL_INPUT);\n   ret = Fl_Input::value(placeholder);\n   position(0);\n   return ret;\n}\n\n/*\n * Set the placeholder text.\n */\nvoid CustInput2::set_placeholder(const char *str)\n{\n   if (placeholder)\n      free(placeholder);\n   placeholder = strdup(str);\n\n   if ((Fl::focus() != this) && !*value()) {\n      show_placeholder();\n   }\n}\n\n/*\n * Set the text color.\n */\nvoid CustInput2::textcolor(Fl_Color c)\n{\n   usual_color = c;\n   if (showing_placeholder)\n      c = fltkui_dimmed(c, color());\n   Fl_Input::textcolor(c);\n}\n\n/*\n * Set the input type (normal, password, etc.)\n */\nvoid CustInput2::input_type(int t)\n{\n   usual_type = t;\n   Fl_Input::input_type(t);\n}\n\n/*\n * Set the value of the input.\n * NOTE that we're not being very careful with the return value, which is\n * supposed to be nonzero iff the value was changed.\n */\nint CustInput2::value(const char *str)\n{\n   return (placeholder && (!str || !*str) && Fl::focus() != this)\n          ? show_placeholder() : show_normal(str);\n}\n\n/*\n * Return the value (text) of the input.\n */\nconst char* CustInput2::value()\n{\n   return showing_placeholder ? \"\" : Fl_Input::value();\n}\n\nint CustInput2::handle(int e)\n{\n   int rc, k = Fl::event_key();\n\n   _MSG(\"CustInput2::handle event=%d\\n\", e);\n\n   // We're only interested in some flags\n   unsigned modifier = Fl::event_state() & (FL_SHIFT | FL_CTRL | FL_ALT);\n\n   if (e == FL_KEYBOARD) {\n      if (k == FL_Page_Down || k == FL_Page_Up || k == FL_Up || k == FL_Down) {\n         // Let them through for key commands and viewport motion.\n         return 0;\n      }\n      if (modifier == FL_CTRL) {\n         if (k == 'A') {\n            position(size(), 0);\n            return 1;\n         } else if (k == 'a' || k == 'e') {\n            position(k == 'a' ? 0 : size());\n            return 1;\n         } else if (k == 'k') {\n            cut(position(), size());\n            return 1;\n         } else if (k == 'd') {\n            cut(position(), position()+1);\n            return 1;\n         } else if (k == 'h' || k == 'i' || k == 'j' || k == 'l' || k == 'm') {\n            // Fl_Input wants to use ^H as backspace, and also \"insert a few\n            // selected control characters literally\", but this gets in the way\n            // of key commands.\n            return 0;\n         }\n      }\n   } else if (e == FL_UNFOCUS) {\n      if (placeholder && !value()[0]) {\n         show_placeholder();\n      }\n   }\n\n   rc = Fl_Input::handle(e);\n\n   if (rc && e == FL_FOCUS) {\n      // Nonzero return from handle() should mean that focus was accepted.\n      if (showing_placeholder)\n         show_normal(\"\");\n   }\n   return rc;\n}\n\n/*\n * Used to show optional placeholder text.\n */\nclass CustTextEditor : public Fl_Text_Editor {\npublic:\n   CustTextEditor (int x, int y, int w, int h, const char* l=0);\n   ~CustTextEditor ();\n   void set_placeholder(const char *str);\n   void show_placeholder();\n   void show_normal(const char *str);\n   void textcolor(Fl_Color c);\n   void value(const char* str);\n   char* value();\n   int handle(int e);\nprivate:\n   char *placeholder;\n   bool showing_placeholder;\n   Fl_Color usual_color;\n   char *text_copy;\n};\n\nCustTextEditor::CustTextEditor (int x, int y, int w, int h, const char* l) :\n                               Fl_Text_Editor(x,y,w,h,l)\n{\n   placeholder = NULL;\n   showing_placeholder = false;\n   buffer(new Fl_Text_Buffer());\n   usual_color = FL_BLACK;      /* just init until widget style is set */\n   text_copy = NULL;\n};\n\nCustTextEditor::~CustTextEditor ()\n{\n   Fl_Text_Buffer *buf = buffer();\n\n   buffer(NULL);\n   delete buf;\n\n   if (placeholder)\n      free(placeholder);\n   if (text_copy)\n      free(text_copy);\n}   \n\n/*\n * Show normal text.\n */\nvoid CustTextEditor::show_normal(const char *str)\n{\n   showing_placeholder = false;\n   Fl_Text_Editor::textcolor(usual_color);\n   buffer()->text(str);\n}\n\n/*\n * Show the placeholder text.\n */\nvoid CustTextEditor::show_placeholder()\n{\n   showing_placeholder = true;\n   Fl_Text_Editor::textcolor(fltkui_dimmed(usual_color, color()));\n   buffer()->text(placeholder);\n}\n\n/*\n * Set the placeholder text.\n */\nvoid CustTextEditor::set_placeholder(const char *str)\n{\n   if (placeholder)\n      free(placeholder);\n   placeholder = strdup(str);\n\n   if ((Fl::focus() != this) && buffer()->length() == 0) {\n      show_placeholder();\n   }\n}\n\n/*\n * Set the text color.\n */\nvoid CustTextEditor::textcolor(Fl_Color c)\n{\n   usual_color = c;\n   if (showing_placeholder)\n      c = fltkui_dimmed(c, color());\n   Fl_Text_Editor::textcolor(c);\n}\n\n/*\n * Set the value of the input.\n */\nvoid CustTextEditor::value(const char *str)\n{\n   if (placeholder && (!str || !*str) && Fl::focus() != this)\n      show_placeholder();\n   else\n      show_normal(str);\n}\n\n/*\n * Return the value (text) of the input.\n */\nchar* CustTextEditor::value()\n{\n   /* FLTK-1.3 insists upon returning a new copy of the buffer text, so\n    * we have to keep track of it.\n    */\n   if (text_copy)\n      free(text_copy);\n   text_copy = showing_placeholder ? strdup(\"\") : buffer()->text();\n   return text_copy;\n}\n\nint CustTextEditor::handle(int e)\n{\n   int rc;\n\n   if (e == FL_UNFOCUS) {\n      if (placeholder && buffer()->length() == 0) {\n         show_placeholder();\n      }\n   }\n\n   rc = Fl_Text_Editor::handle(e);\n\n   if (rc && e == FL_FOCUS) {\n      // Nonzero return from handle() should mean that focus was accepted.\n      if (showing_placeholder)\n         show_normal(\"\");\n   }\n   return rc;\n}\n\n\n/*\n * Used to handle some keystrokes as shortcuts to option menuitems\n * (i.e. jump to the next menuitem whose label starts with the pressed key)\n */\nclass CustChoice : public Fl_Choice {\npublic:\n   CustChoice (int x, int y, int w, int h, const char* l=0) :\n      Fl_Choice(x,y,w,h,l) {};\n   int handle(int e);\n};\n\nint CustChoice::handle(int e)\n{\n   int k = Fl::event_key();\n   unsigned modifier = Fl::event_state() & (FL_SHIFT|FL_CTRL|FL_ALT|FL_META);\n\n   _MSG(\"CustChoice::handle %p e=%d active=%d focus=%d\\n\",\n       this, e, active(), (Fl::focus() == this));\n   if (Fl::focus() != this) {\n      ; // Not Focused, let FLTK handle it\n   } else if (e == FL_KEYDOWN && modifier == 0) {\n      if (k == FL_Enter || k == FL_Down) {\n         return Fl_Choice::handle(FL_PUSH); // activate menu\n\n      } else if (isalnum(k)) { // try key as shortcut to menuitem\n         int t = value()+1 >= size() ? 0 : value()+1;\n         while (t != value()) {\n             const Fl_Menu_Item *mi = &(menu()[t]);\n             if (mi->submenu()) // submenu?\n                ;\n             else if (mi->label() && mi->active()) { // menu item?\n                if (k == tolower(mi->label()[0])) {\n                   value(mi);\n                   return 1; // Let FLTK know we used this key\n                }\n             }\n             if (++t == size())\n                t = 0;\n         }\n      }\n   }\n\n   return Fl_Choice::handle(e);\n}\n\n//----------------------------------------------------------------------------\n\nnamespace dw {\nnamespace fltk {\nnamespace ui {\n\nenum { RELIEF_X_THICKNESS = 3, RELIEF_Y_THICKNESS = 3 };\n\nusing namespace lout::object;\nusing namespace lout::container::typed;\n\nFltkResource::FltkResource (FltkPlatform *platform)\n{\n   DBG_OBJ_CREATE (\"dw::fltk::ui::FltkResource\");\n\n   this->platform = platform;\n\n   allocation.x = 0;\n   allocation.y = 0;\n   allocation.width = 1;\n   allocation.ascent = 1;\n   allocation.descent = 0;\n\n   style = NULL;\n\n   enabled = true;\n}\n\n/**\n * This is not a constructor, since it calls some virtual methods, which\n * should not be done in a C++ base constructor.\n */\nvoid FltkResource::init (FltkPlatform *platform)\n{\n   view = NULL;\n   widget = NULL;\n   platform->attachResource (this);\n}\n\nFltkResource::~FltkResource ()\n{\n   platform->detachResource (this);\n   if (widget) {\n      if (view) {\n         view->removeFltkWidget(widget);\n      }\n      delete widget;\n   }\n   if (style)\n      style->unref ();\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid FltkResource::attachView (FltkView *view)\n{\n   if (this->view)\n      MSG_ERR(\"FltkResource::attachView: multiple views!\\n\");\n\n   if (view->usesFltkWidgets ()) {\n      this->view = view;\n\n      widget = createNewWidget (&allocation);\n      view->addFltkWidget (widget, &allocation);\n      if (style)\n         setWidgetStyle (widget, style);\n      if (! enabled)\n         widget->deactivate ();\n   }\n}\n\nvoid FltkResource::detachView (FltkView *view)\n{\n   if (this->view != view)\n      MSG_ERR(\"FltkResource::detachView: this->view: %p view: %p\\n\",\n              this->view, view);\n   this->view = NULL;\n}\n\nvoid FltkResource::sizeAllocate (core::Allocation *allocation)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"sizeAllocate\", \"%d, %d; %d * (%d + %d)\",\n                  allocation->x, allocation->y, allocation->width,\n                  allocation->ascent, allocation->descent);\n\n   this->allocation = *allocation;\n   view->allocateFltkWidget (widget, allocation);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid FltkResource::draw (core::View *view, core::Rectangle *area,\n                         core::DrawingContext *context)\n{\n   FltkView *fltkView = (FltkView*)view;\n   if (fltkView->usesFltkWidgets () && this->view == fltkView) {\n      fltkView->drawFltkWidget (widget, area);\n   }\n}\n\nvoid FltkResource::setStyle (core::style::Style *style)\n{\n   if (this->style)\n      this->style->unref ();\n\n   this->style = style;\n   style->ref ();\n\n   setWidgetStyle (widget, style);\n}\n\nvoid FltkResource::setWidgetStyle (Fl_Widget *widget,\n                                   core::style::Style *style)\n{\n   FltkFont *font = (FltkFont*)style->font;\n   widget->labelsize (font->size);\n   widget->labelfont (font->font);\n\n   FltkColor *bg = (FltkColor*)style->backgroundColor;\n   if (bg) {\n      int normal_bg = bg->colors[FltkColor::SHADING_NORMAL];\n\n      if (style->color) {\n         int style_fg = ((FltkColor*)style->color)->colors\n                                                   [FltkColor::SHADING_NORMAL];\n         Fl_Color fg = fl_contrast(style_fg, normal_bg);\n\n         widget->labelcolor(fg);\n         widget->selection_color(fg);\n      }\n\n      widget->color(normal_bg);\n   }\n}\n\nvoid FltkResource::setDisplayed(bool displayed)\n{\n   if (displayed)\n      widget->show();\n   else\n      widget->hide();\n}\n\nbool FltkResource::displayed()\n{\n   bool ret = false;\n\n   if (widget) {\n      // visible() is not the same thing as being show()n exactly, but\n      // show()/hide() set it appropriately for our purposes.\n      ret = widget->visible();\n   }\n   return ret;\n}\n\nbool FltkResource::isEnabled ()\n{\n   return enabled;\n}\n\nvoid FltkResource::setEnabled (bool enabled)\n{\n   this->enabled = enabled;\n\n   if (enabled)\n      widget->activate ();\n   else\n      widget->deactivate ();\n}\n\n// ----------------------------------------------------------------------\n\ntemplate <class I> FltkSpecificResource<I>::FltkSpecificResource (FltkPlatform\n                                                                  *platform) :\n   FltkResource (platform)\n{\n   DBG_OBJ_CREATE (\"dw::fltk::ui::FltkSpecificResource<>\");\n   DBG_OBJ_BASECLASS (I);\n   DBG_OBJ_BASECLASS (FltkResource);\n}\n\ntemplate <class I> FltkSpecificResource<I>::~FltkSpecificResource ()\n{\n   DBG_OBJ_DELETE ();\n}\n\ntemplate <class I> void FltkSpecificResource<I>::sizeAllocate (core::Allocation\n                                                               *allocation)\n{\n   FltkResource::sizeAllocate (allocation);\n}\n\ntemplate <class I> void FltkSpecificResource<I>::draw (core::View *view,\n                                                       core::Rectangle *area,\n                                                       core::DrawingContext\n                                                       *context)\n{\n   FltkResource::draw (view, area, context);\n}\n\ntemplate <class I> void FltkSpecificResource<I>::setStyle (core::style::Style\n                                                           *style)\n{\n   FltkResource::setStyle (style);\n}\n\ntemplate <class I> bool FltkSpecificResource<I>::isEnabled ()\n{\n   return FltkResource::isEnabled ();\n}\n\ntemplate <class I> void FltkSpecificResource<I>::setEnabled (bool enabled)\n{\n   FltkResource::setEnabled (enabled);\n}\n\n// ----------------------------------------------------------------------\n\nclass EnterButton : public Fl_Button {\npublic:\n   EnterButton (int x,int y,int w,int h, const char* label = 0) :\n      Fl_Button (x,y,w,h,label) {};\n   int handle(int e);\n};\n\nint EnterButton::handle(int e)\n{\n   if (e == FL_KEYBOARD && Fl::focus() == this && Fl::event_key() == FL_Enter){\n      set_changed();\n      simulate_key_action();\n      do_callback();\n      return 1;\n   }\n   return Fl_Button::handle(e);\n}\n\nFltkLabelButtonResource::FltkLabelButtonResource (FltkPlatform *platform,\n                                                  const char *label):\n   FltkSpecificResource <dw::core::ui::LabelButtonResource> (platform)\n{\n   this->label = strdup (label);\n   init (platform);\n}\n\nFltkLabelButtonResource::~FltkLabelButtonResource ()\n{\n   free((char *)label);\n}\n\nFl_Widget *FltkLabelButtonResource::createNewWidget (core::Allocation\n                                                     *allocation)\n{\n   Fl_Button *button =\n        new EnterButton (allocation->x, allocation->y, allocation->width,\n                         allocation->ascent + allocation->descent, label);\n   button->callback (widgetCallback, this);\n   button->when (FL_WHEN_RELEASE);\n   return button;\n}\n\nvoid FltkLabelButtonResource::sizeRequest (core::Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequest\");\n\n   if (style) {\n      FltkFont *font = (FltkFont*)style->font;\n      fl_font(font->font,font->size);\n      requisition->width =\n         (int)fl_width (label, strlen (label))\n         + 2 * RELIEF_X_THICKNESS;\n      requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;\n      requisition->descent = font->descent + RELIEF_Y_THICKNESS;\n   } else {\n      requisition->width = 1;\n      requisition->ascent = 1;\n      requisition->descent = 0;\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\n/*\n * Get FLTK state and translate to dw\n *\n * TODO: find a good home for this and the fltkviewbase.cc original.\n */\nstatic core::ButtonState getDwButtonState ()\n{\n   int s1 = Fl::event_state ();\n   int s2 = (core::ButtonState)0;\n\n   if (s1 & FL_SHIFT)   s2 |= core::SHIFT_MASK;\n   if (s1 & FL_CTRL)    s2 |= core::CONTROL_MASK;\n   if (s1 & FL_ALT)     s2 |= core::META_MASK;\n   if (s1 & FL_BUTTON1) s2 |= core::BUTTON1_MASK;\n   if (s1 & FL_BUTTON2) s2 |= core::BUTTON2_MASK;\n   if (s1 & FL_BUTTON3) s2 |= core::BUTTON3_MASK;\n\n   return (core::ButtonState)s2;\n}\n\nstatic void setButtonEvent(dw::core::EventButton *event)\n{\n   event->xCanvas = Fl::event_x();\n   event->yCanvas = Fl::event_y();\n   event->state = getDwButtonState();\n   event->button = Fl::event_button();\n   event->numPressed = Fl::event_clicks() + 1;\n}\n\nvoid FltkLabelButtonResource::widgetCallback (Fl_Widget *widget,\n                                              void *data)\n{\n   if (!Fl::event_button3()) {\n      FltkLabelButtonResource *lbr = (FltkLabelButtonResource*) data;\n      dw::core::EventButton event;\n      setButtonEvent(&event);\n      lbr->emitClicked(&event);\n   }\n}\n\nconst char *FltkLabelButtonResource::getLabel ()\n{\n   return label;\n}\n\n\nvoid FltkLabelButtonResource::setLabel (const char *label)\n{\n   free((char *)this->label);\n   this->label = strdup (label);\n\n   widget->label (this->label);\n   queueResize (true);\n}\n\n// ----------------------------------------------------------------------\n\nFltkComplexButtonResource::FltkComplexButtonResource (FltkPlatform *platform,\n                                                      dw::core::Widget\n                                                      *widget, bool relief):\n   FltkSpecificResource <dw::core::ui::ComplexButtonResource> (platform)\n{\n   flatView = topView = NULL;\n   this->relief = relief;\n   FltkResource::init (platform);\n   ComplexButtonResource::init (widget);\n}\n\nFltkComplexButtonResource::~FltkComplexButtonResource ()\n{\n}\n\nvoid FltkComplexButtonResource::widgetCallback (Fl_Widget *widget,\n                                                void *data)\n{\n   FltkComplexButtonResource *res = (FltkComplexButtonResource*)data;\n\n   if (Fl::event() == FL_RELEASE && Fl::event_button() != FL_RIGHT_MOUSE) {\n      int w = widget->w(), h = widget->h();\n\n      res->click_x = Fl::event_x() - widget->x();\n      res->click_y = Fl::event_y() - widget->y();\n      if (res->style) {\n         res->click_x -= res->style->boxOffsetX();\n         res->click_y -= res->style->boxOffsetY();\n         w -= res->style->boxDiffWidth();\n         h -= res->style->boxDiffHeight();\n      }\n      if (res->click_x >= 0 && res->click_y >= 0 &&\n          res->click_x < w && res->click_y < h) {\n         dw::core::EventButton event;\n         setButtonEvent(&event);\n         res->emitClicked(&event);\n      }\n   } else if (Fl::event() == FL_KEYBOARD) {\n      // Simulate a click.\n      dw::core::EventButton event;\n\n      res->click_x = res->click_y = 0;\n      event.xCanvas = widget->x() + res->style->boxOffsetX();\n      event.yCanvas = widget->y() + res->style->boxOffsetY();\n      // ButtonState doesn't have mouse button values on a release.\n      event.state = (core::ButtonState) 0;\n      event.button = 1;\n      event.numPressed = 1;\n      res->emitClicked(&event);\n   }\n}\n\ndw::core::Platform *FltkComplexButtonResource::createPlatform ()\n{\n   return new FltkPlatform ();\n}\n\nvoid FltkComplexButtonResource::attachView (FltkView *view)\n{\n   FltkResource::attachView (view);\n\n   if (view->usesFltkWidgets ())\n      topView = view;\n}\n\nvoid FltkComplexButtonResource::detachView (FltkView *view)\n{\n   FltkResource::detachView (view);\n}\n\nvoid FltkComplexButtonResource::sizeAllocate (core::Allocation *allocation)\n{\n   FltkResource::sizeAllocate (allocation);\n\n   DBG_OBJ_MSGF_O (\"resize\", 0, flatView,\n                   \"<b>resize</b> (%d %d, <i>%d - 2 * %d =</i> %d, \"\n                   \"<i>%d + %d - 2 * %d =</i> %d)\",\n                   reliefXThickness (), reliefYThickness (),\n                   allocation->width, reliefXThickness (),\n                   allocation->width - 2 * reliefXThickness (),\n                   allocation->ascent, allocation->descent,\n                   reliefYThickness (),\n                   allocation->ascent + allocation->descent\n                   - 2 * reliefYThickness ());\n\n   ((FltkFlatView*)flatView)->resize (\n      reliefXThickness (), reliefYThickness (),\n      allocation->width - 2 * reliefXThickness (),\n      allocation->ascent + allocation->descent - 2 * reliefYThickness ());\n\n   ((FltkFlatView*)flatView)->parent ()->init_sizes ();\n}\n\nvoid FltkComplexButtonResource::setLayout (dw::core::Layout *layout)\n{\n   layout->attachView (flatView);\n}\n\nint FltkComplexButtonResource::reliefXThickness ()\n{\n   return relief ? RELIEF_X_THICKNESS : 0;\n}\n\nint FltkComplexButtonResource::reliefYThickness ()\n{\n   return relief ? RELIEF_Y_THICKNESS : 0;\n}\n\n\nFl_Widget *FltkComplexButtonResource::createNewWidget (core::Allocation\n                                                            *allocation)\n{\n   ComplexButton *button =\n      new ComplexButton (allocation->x, allocation->y, allocation->width,\n                         allocation->ascent + allocation->descent);\n   button->callback (widgetCallback, this);\n   button->when (FL_WHEN_RELEASE);\n   if (!relief)\n      button->box(FL_NO_BOX);\n\n   flatView = new FltkFlatView (allocation->x + reliefXThickness (),\n                                allocation->y + reliefYThickness (),\n                                allocation->width - 2 * reliefXThickness (),\n                                allocation->ascent + allocation->descent\n                                   - 2 * reliefYThickness ());\n   button->add ((FltkFlatView *)flatView);\n\n   if (layout)\n      layout->attachView (flatView);\n   return button;\n}\n\n// ----------------------------------------------------------------------\n\nFltkEntryResource::FltkEntryResource (FltkPlatform *platform, int size,\n                                      bool password, const char *label,\n                                      const char *placeholder):\n   FltkSpecificResource <dw::core::ui::EntryResource> (platform)\n{\n   this->size = size;\n   this->password = password;\n   this->label = label ? strdup(label) : NULL;\n   this->label_w = 0;\n   this->placeholder = placeholder ? strdup(placeholder) : NULL;\n\n   initText = NULL;\n   editable = false;\n\n   init (platform);\n}\n\nFltkEntryResource::~FltkEntryResource ()\n{\n   if (initText)\n      free((char *)initText);\n   if (label)\n      free(label);\n   if (placeholder)\n      free(placeholder);\n}\n\nFl_Widget *FltkEntryResource::createNewWidget (core::Allocation\n                                                    *allocation)\n{\n   CustInput2 *input =\n        new CustInput2(allocation->x, allocation->y, allocation->width,\n                      allocation->ascent + allocation->descent);\n   input->input_type(password ? FL_SECRET_INPUT : FL_NORMAL_INPUT);\n   input->callback (widgetCallback, this);\n   input->when (FL_WHEN_ENTER_KEY_ALWAYS);\n\n   if (label) {\n      input->label(label);\n      input->align(FL_ALIGN_LEFT);\n   }\n   if (initText)\n      input->value (initText);\n   if (placeholder)\n      input->set_placeholder(placeholder);\n\n   return input;\n}\n\nvoid FltkEntryResource::setWidgetStyle (Fl_Widget *widget,\n                                        core::style::Style *style)\n{\n   CustInput2 *in = (CustInput2 *)widget;\n\n   FltkResource::setWidgetStyle(widget, style);\n\n   in->textcolor(widget->labelcolor());\n   in->cursor_color(widget->labelcolor());\n   in->textsize(in->labelsize());\n   in->textfont(in->labelfont());\n\n   if (label) {\n      int h;\n      label_w = 0;\n      widget->measure_label(label_w, h);\n      label_w += RELIEF_X_THICKNESS;\n   }\n}\n\nvoid FltkEntryResource::setDisplayed(bool displayed)\n{\n   FltkResource::setDisplayed(displayed);\n   queueResize(true);\n}\n\nvoid FltkEntryResource::sizeRequest (core::Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequest\");\n\n   if (displayed() && style) {\n      FltkFont *font = (FltkFont*)style->font;\n      fl_font(font->font,font->size);\n      // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in\n      // 1.3.0 (STR #2688).\n      requisition->width =\n         (int)fl_width (\"n\")\n         * (size == UNLIMITED_SIZE ? 10 : size)\n         + label_w + (2 * RELIEF_X_THICKNESS);\n      requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;\n      requisition->descent = font->descent + RELIEF_Y_THICKNESS;\n   } else {\n      requisition->width = 0;\n      requisition->ascent = 0;\n      requisition->descent = 0;\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid FltkEntryResource::sizeAllocate (core::Allocation *allocation)\n{\n   if (!label) {\n      FltkResource::sizeAllocate(allocation);\n   } else {\n      DBG_OBJ_MSGF (\"resize\", 0,\n                    \"<b>sizeAllocate</b> (%d, %d; %d * (%d + %d))\",\n                    allocation->x, allocation->y, allocation->width,\n                    allocation->ascent, allocation->descent);\n\n      this->allocation = *allocation;\n\n      /* push the Fl_Input over to the right of the label */\n      core::Allocation a = this->allocation;\n      a.x += this->label_w;\n      a.width -= this->label_w;\n      view->allocateFltkWidget (widget, &a);\n   }\n}\n\nvoid FltkEntryResource::widgetCallback (Fl_Widget *widget, void *data)\n{\n   ((FltkEntryResource*)data)->emitActivate ();\n}\n\nconst char *FltkEntryResource::getText ()\n{\n   return ((CustInput2*)widget)->value ();\n}\n\nvoid FltkEntryResource::setText (const char *text)\n{\n   if (initText)\n      free((char *)initText);\n   initText = strdup (text);\n\n   ((CustInput2*)widget)->value (initText);\n}\n\nbool FltkEntryResource::isEditable ()\n{\n   return editable;\n}\n\nvoid FltkEntryResource::setEditable (bool editable)\n{\n   this->editable = editable;\n}\n\nvoid FltkEntryResource::setMaxLength (int maxlen)\n{\n   ((Fl_Input *)widget)->maximum_size(maxlen);\n}\n\n// ----------------------------------------------------------------------\n\nstatic int kf_backspace_word (int c, Fl_Text_Editor *e)\n{\n   int p1, p2 = e->insert_position();\n\n   e->previous_word();\n   p1 = e->insert_position();\n   e->buffer()->remove(p1, p2);\n   e->show_insert_position();\n   e->set_changed();\n   if (e->when() & FL_WHEN_CHANGED)\n      e->do_callback();\n   return 0;\n}\n\nFltkMultiLineTextResource::FltkMultiLineTextResource (FltkPlatform *platform,\n                                                      int cols, int rows,\n                                                      const char *placeholder):\n   FltkSpecificResource <dw::core::ui::MultiLineTextResource> (platform)\n{\n   editable = false;\n\n   numCols = cols;\n   numRows = rows;\n\n   DBG_OBJ_SET_NUM (\"numCols\", numCols);\n   DBG_OBJ_SET_NUM (\"numRows\", numRows);\n\n   // Check values. Upper bound check is left to the caller.\n   if (numCols < 1) {\n      MSG_WARN(\"numCols = %d is set to 1.\\n\", numCols);\n      numCols = 1;\n   }\n   if (numRows < 1) {\n      MSG_WARN(\"numRows = %d is set to 1.\\n\", numRows);\n      numRows = 1;\n   }\n   this->placeholder = placeholder ? strdup(placeholder) : NULL;\n\n   init (platform);\n}\n\nFltkMultiLineTextResource::~FltkMultiLineTextResource ()\n{\n   if (placeholder)\n      free(placeholder);\n}\n\nFl_Widget *FltkMultiLineTextResource::createNewWidget (core::Allocation\n                                                            *allocation)\n{\n   CustTextEditor *text =\n      new CustTextEditor (allocation->x, allocation->y, allocation->width,\n                          allocation->ascent + allocation->descent);\n   text->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0);\n   text->remove_key_binding(FL_BackSpace, FL_TEXT_EDITOR_ANY_STATE);\n   text->add_key_binding(FL_BackSpace, 0, Fl_Text_Editor::kf_backspace);\n   text->add_key_binding(FL_BackSpace, FL_CTRL, kf_backspace_word);\n   if (placeholder)\n      text->set_placeholder(placeholder);\n   return text;\n}\n\nvoid FltkMultiLineTextResource::setWidgetStyle (Fl_Widget *widget,\n                                                core::style::Style *style)\n{\n   CustTextEditor *ed = (CustTextEditor *)widget;\n\n   FltkResource::setWidgetStyle(widget, style);\n\n   ed->textcolor(widget->labelcolor());\n   ed->cursor_color(widget->labelcolor());\n   ed->textsize(ed->labelsize());\n   ed->textfont(ed->labelfont());\n}\n\nvoid FltkMultiLineTextResource::sizeRequest (core::Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequest\");\n\n   if (style) {\n      FltkFont *font = (FltkFont*)style->font;\n      fl_font(font->font,font->size);\n      // WORKAROUND: A bug with fl_width(uint_t) on non-xft X was present in\n      // 1.3.0 (STR #2688).\n      requisition->width =\n         (int)fl_width (\"n\") * numCols + 2 * RELIEF_X_THICKNESS;\n      requisition->ascent =\n         RELIEF_Y_THICKNESS + font->ascent +\n         (font->ascent + font->descent) * (numRows - 1);\n      requisition->descent =\n         font->descent +\n         RELIEF_Y_THICKNESS;\n   } else {\n      requisition->width = 1;\n      requisition->ascent = 1;\n      requisition->descent = 0;\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\nconst char *FltkMultiLineTextResource::getText ()\n{\n   return ((CustTextEditor*)widget)->value ();\n}\n\nvoid FltkMultiLineTextResource::setText (const char *text)\n{\n   ((CustTextEditor*)widget)->value (text);\n}\n\nbool FltkMultiLineTextResource::isEditable ()\n{\n   return editable;\n}\n\nvoid FltkMultiLineTextResource::setEditable (bool editable)\n{\n   this->editable = editable;\n}\n\n// ----------------------------------------------------------------------\n\ntemplate <class I>\nFltkToggleButtonResource<I>::FltkToggleButtonResource (FltkPlatform *platform,\n                                                       bool activated):\n   FltkSpecificResource <I> (platform)\n{\n   initActivated = activated;\n}\n\n\ntemplate <class I>\nFltkToggleButtonResource<I>::~FltkToggleButtonResource ()\n{\n}\n\n\ntemplate <class I>\nFl_Widget *FltkToggleButtonResource<I>::createNewWidget (core::Allocation\n                                                              *allocation)\n{\n   Fl_Button *button = createNewButton (allocation);\n   button->value (initActivated);\n   return button;\n}\n\ntemplate <class I>\nvoid FltkToggleButtonResource<I>::setWidgetStyle (Fl_Widget *widget,\n                                                  core::style::Style *style)\n{\n   FltkResource::setWidgetStyle(widget, style);\n\n   widget->selection_color(FL_BLACK);\n}\n\n\ntemplate <class I>\nvoid FltkToggleButtonResource<I>::sizeRequest (core::Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequest\");\n\n   FltkFont *font = (FltkFont *)\n      (this->FltkResource::style ? this->FltkResource::style->font : NULL);\n\n   if (font) {\n      fl_font(font->font, font->size);\n      requisition->width = font->ascent + font->descent + 2*RELIEF_X_THICKNESS;\n      requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;\n      requisition->descent = font->descent + RELIEF_Y_THICKNESS;\n   } else {\n      requisition->width = 1;\n      requisition->ascent = 1;\n      requisition->descent = 0;\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\n\ntemplate <class I>\nbool FltkToggleButtonResource<I>::isActivated ()\n{\n   return ((Fl_Button*)this->widget)->value ();\n}\n\n\ntemplate <class I>\nvoid FltkToggleButtonResource<I>::setActivated (bool activated)\n{\n   initActivated = activated;\n   ((Fl_Button*)this->widget)->value (initActivated);\n}\n\n// ----------------------------------------------------------------------\n\nFltkCheckButtonResource::FltkCheckButtonResource (FltkPlatform *platform,\n                                                  bool activated):\n   FltkToggleButtonResource<dw::core::ui::CheckButtonResource> (platform,\n                                                                activated)\n{\n   init (platform);\n}\n\n\nFltkCheckButtonResource::~FltkCheckButtonResource ()\n{\n}\n\n\nFl_Button *FltkCheckButtonResource::createNewButton (core::Allocation\n                                                          *allocation)\n{\n   Fl_Check_Button *cb =\n      new Fl_Check_Button (allocation->x, allocation->y, allocation->width,\n                           allocation->ascent + allocation->descent);\n   return cb;\n}\n\n// ----------------------------------------------------------------------\n\nbool FltkRadioButtonResource::Group::FltkGroupIterator::hasNext ()\n{\n   return it.hasNext ();\n}\n\ndw::core::ui::RadioButtonResource\n*FltkRadioButtonResource::Group::FltkGroupIterator::getNext ()\n{\n   return (dw::core::ui::RadioButtonResource*)it.getNext ();\n}\n\nvoid FltkRadioButtonResource::Group::FltkGroupIterator::unref ()\n{\n   delete this;\n}\n\n\nFltkRadioButtonResource::Group::Group (FltkRadioButtonResource\n                                       *radioButtonResource)\n{\n   list = new lout::container::typed::List <FltkRadioButtonResource> (false);\n   connect (radioButtonResource);\n}\n\nFltkRadioButtonResource::Group::~Group ()\n{\n   delete list;\n}\n\nvoid FltkRadioButtonResource::Group::connect (FltkRadioButtonResource\n                                              *radioButtonResource)\n{\n   list->append (radioButtonResource);\n}\n\nvoid FltkRadioButtonResource::Group::unconnect (FltkRadioButtonResource\n                                                *radioButtonResource)\n{\n   list->removeRef (radioButtonResource);\n   if (list->isEmpty ())\n      delete this;\n}\n\n\nFltkRadioButtonResource::FltkRadioButtonResource (FltkPlatform *platform,\n                                                  FltkRadioButtonResource\n                                                  *groupedWith,\n                                                  bool activated):\n   FltkToggleButtonResource<dw::core::ui::RadioButtonResource> (platform,\n                                                                activated)\n{\n   init (platform);\n\n   if (groupedWith) {\n      group = groupedWith->group;\n      group->connect (this);\n   } else\n      group = new Group (this);\n}\n\n\nFltkRadioButtonResource::~FltkRadioButtonResource ()\n{\n   group->unconnect (this);\n}\n\ndw::core::ui::RadioButtonResource::GroupIterator\n*FltkRadioButtonResource::groupIterator ()\n{\n   return group->groupIterator ();\n}\n\nvoid FltkRadioButtonResource::widgetCallback (Fl_Widget *widget,\n                                              void *data)\n{\n   if (widget->when () & FL_WHEN_CHANGED)\n      ((FltkRadioButtonResource*)data)->buttonClicked ();\n}\n\nvoid FltkRadioButtonResource::buttonClicked ()\n{\n   for (Iterator <FltkRadioButtonResource> it = group->iterator ();\n        it.hasNext (); ) {\n      FltkRadioButtonResource *other = it.getNext ();\n      other->setActivated (other == this);\n   }\n}\n\nFl_Button *FltkRadioButtonResource::createNewButton (core::Allocation\n                                                     *allocation)\n{\n   /*\n    * Groups of Fl_Radio_Button must be added to one Fl_Group, which is\n    * not possible in this context. For this, we do the grouping ourself,\n    * based on FltkRadioButtonResource::Group.\n    *\n    * What we actually need for this, is a widget, which behaves like a\n    * check button, but looks like a radio button. The first depends on the\n    * type, the second on the style. Since the type is simpler to change\n    * than the style, we create a radio button, and then change the type\n    * (instead of creating a check button, and changing the style).\n    */\n\n   Fl_Button *button =\n      new Fl_Round_Button (allocation->x, allocation->y, allocation->width,\n                           allocation->ascent + allocation->descent);\n   button->when (FL_WHEN_CHANGED);\n   button->callback (widgetCallback, this);\n   button->type (FL_TOGGLE_BUTTON);\n\n   return button;\n}\n\n// ----------------------------------------------------------------------\n\ntemplate <class I> dw::core::Iterator *\nFltkSelectionResource<I>::iterator (dw::core::Content::Type mask, bool atEnd)\n{\n   /** \\bug Implementation. */\n   return new core::EmptyIterator (this->getEmbed (), mask, atEnd);\n}\n\n// ----------------------------------------------------------------------\n\nFltkOptionMenuResource::FltkOptionMenuResource (FltkPlatform *platform):\n   FltkSelectionResource <dw::core::ui::OptionMenuResource> (platform)\n{\n   /* Fl_Menu_ does not like multiple menu items with the same label, and\n    * insert() treats some characters specially unless escaped, so let's\n    * do our own menu handling.\n    */\n   itemsAllocated = 0x10;\n   menu = new Fl_Menu_Item[itemsAllocated];\n   memset(menu, 0, itemsAllocated * sizeof(Fl_Menu_Item));\n   itemsUsed = 1; // menu[0].text == NULL, which is an end-of-menu marker.\n\n   init (platform);\n}\n\nFltkOptionMenuResource::~FltkOptionMenuResource ()\n{\n   for (int i = 0; i < itemsUsed; i++) {\n      if (menu[i].text)\n         free((char *) menu[i].text);\n   }\n   delete[] menu;\n}\n\nvoid FltkOptionMenuResource::setWidgetStyle (Fl_Widget *widget,\n                                             core::style::Style *style)\n{\n   Fl_Choice *ch = (Fl_Choice *)widget;\n\n   FltkResource::setWidgetStyle(widget, style);\n\n   ch->textcolor(widget->labelcolor());\n   ch->textfont(ch->labelfont());\n   ch->textsize(ch->labelsize());\n}\n\nFl_Widget *FltkOptionMenuResource::createNewWidget (core::Allocation\n                                                     *allocation)\n{\n   Fl_Choice *choice =\n      new CustChoice (allocation->x, allocation->y,\n                      allocation->width,\n                      allocation->ascent + allocation->descent);\n   choice->menu(menu);\n   return choice;\n}\n\nvoid FltkOptionMenuResource::widgetCallback (Fl_Widget *widget,\n                                             void *data)\n{\n}\n\nint FltkOptionMenuResource::getMaxItemWidth()\n{\n   int i, max = 0;\n\n   for (i = 0; i < itemsUsed; i++) {\n      int width = 0;\n      const char *str = menu[i].text;\n\n      if (str) {\n         width = fl_width(str);\n         if (width > max)\n            max = width;\n      }\n   }\n   return max;\n}\n\nvoid FltkOptionMenuResource::sizeRequest (core::Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequest\");\n\n   if (style) {\n      FltkFont *font = (FltkFont*)style->font;\n      fl_font(font->font, font->size);\n      int maxItemWidth = getMaxItemWidth ();\n      requisition->ascent = font->ascent + RELIEF_Y_THICKNESS;\n      requisition->descent = font->descent + RELIEF_Y_THICKNESS;\n      requisition->width = maxItemWidth\n         + (requisition->ascent + requisition->descent)\n         + 2 * RELIEF_X_THICKNESS;\n   } else {\n      requisition->width = 1;\n      requisition->ascent = 1;\n      requisition->descent = 0;\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid FltkOptionMenuResource::enlargeMenu ()\n{\n   Fl_Choice *ch = (Fl_Choice *)widget;\n   int selected = ch->value();\n   Fl_Menu_Item *newMenu;\n\n   itemsAllocated += 0x10;\n   newMenu = new Fl_Menu_Item[itemsAllocated];\n   memcpy(newMenu, menu, itemsUsed * sizeof(Fl_Menu_Item));\n   memset(newMenu + itemsUsed, 0, 0x10 * sizeof(Fl_Menu_Item));\n   delete[] menu;\n   menu = newMenu;\n   ch->menu(menu);\n   ch->value(selected);\n}\n\nFl_Menu_Item *FltkOptionMenuResource::newItem()\n{\n   Fl_Menu_Item *item;\n\n   if (itemsUsed == itemsAllocated)\n      enlargeMenu();\n\n   item = menu + itemsUsed - 1;\n   itemsUsed++;\n\n   return item;\n}\n\nvoid FltkOptionMenuResource::addItem (const char *str,\n                                      bool enabled, bool selected)\n{\n   Fl_Menu_Item *item = newItem();\n\n   item->text = strdup(str);\n\n   if (enabled == false)\n      item->flags = FL_MENU_INACTIVE;\n\n   if (selected)\n      ((Fl_Choice *)widget)->value(item);\n\n   queueResize (true);\n}\n\nvoid FltkOptionMenuResource::setItem (int index, bool selected)\n{\n   if (selected)\n      ((Fl_Choice *)widget)->value(menu+index);\n}\n\nvoid FltkOptionMenuResource::pushGroup (const char *name, bool enabled)\n{\n   Fl_Menu_Item *item = newItem();\n\n   item->text = strdup(name);\n\n   if (enabled == false)\n      item->flags = FL_MENU_INACTIVE;\n\n   item->flags |= FL_SUBMENU;\n\n   queueResize (true);\n}\n\nvoid FltkOptionMenuResource::popGroup ()\n{\n   /* Item with NULL text field closes the submenu */\n   newItem();\n   queueResize (true);\n}\n\nbool FltkOptionMenuResource::isSelected (int index)\n{\n   return index == ((Fl_Choice *)widget)->value();\n}\n\nint FltkOptionMenuResource::getNumberOfItems()\n{\n   return ((Fl_Choice*)widget)->size();\n}\n\n// ----------------------------------------------------------------------\n\nclass CustBrowser : public Fl_Browser {\npublic:\n   CustBrowser(int x, int y, int w, int h) : Fl_Browser(x, y, w, h) {};\n   int full_width() const;\n   int full_height() const {return Fl_Browser::full_height();}\n   int avg_height() {return size() ? Fl_Browser_::incr_height() : 0;}\n};\n\n/*\n * Fl_Browser_ has a full_width(), but it has a tendency to contain 0, so...\n */\nint CustBrowser::full_width() const\n{\n   int max = 0;\n   void *item = item_first();\n\n   while (item) {\n      int w = item_width(item);\n\n      if (w > max)\n         max = w;\n\n      item = item_next(item);\n   }\n   return max;\n}\n\nFltkListResource::FltkListResource (FltkPlatform *platform,\n                                    core::ui::ListResource::SelectionMode\n                                    selectionMode, int rowCount):\n   FltkSelectionResource <dw::core::ui::ListResource> (platform),\n   currDepth(0)\n{\n   mode = selectionMode;\n   showRows = rowCount;\n   init (platform);\n}\n\nFltkListResource::~FltkListResource ()\n{\n}\n\n\nFl_Widget *FltkListResource::createNewWidget (core::Allocation *allocation)\n{\n   CustBrowser *b =\n      new CustBrowser (allocation->x, allocation->y, allocation->width,\n                      allocation->ascent + allocation->descent);\n\n   b->type((mode == SELECTION_MULTIPLE) ? FL_MULTI_BROWSER : FL_HOLD_BROWSER);\n   b->callback(widgetCallback, this);\n   b->when(FL_WHEN_CHANGED);\n   b->column_widths(colWidths);\n   b->column_char('\\a');   // I just chose a nonprinting character.\n\n   return b;\n}\n\nvoid FltkListResource::setWidgetStyle (Fl_Widget *widget,\n                                       core::style::Style *style)\n{\n   Fl_Browser *b = (Fl_Browser *)widget;\n\n   FltkResource::setWidgetStyle(widget, style);\n\n   b->textfont(widget->labelfont());\n   b->textsize(widget->labelsize());\n   b->textcolor(widget->labelcolor());\n\n   colWidths[0] = b->textsize();\n   colWidths[1] = colWidths[0];\n   colWidths[2] = colWidths[0];\n   colWidths[3] = 0;\n}\n\nvoid FltkListResource::widgetCallback (Fl_Widget *widget, void *data)\n{\n   Fl_Browser *b = (Fl_Browser *) widget;\n\n   if (b->selected(b->value())) {\n      /* If it shouldn't be selectable, deselect it again. It would be nice to\n       * have a less unpleasant way to do this.\n       */\n      const char *inactive_code;\n      if ((inactive_code = strstr(b->text(b->value()), \"@N\"))) {\n         const char *ignore_codes = strstr(b->text(b->value()), \"@.\");\n\n         if (inactive_code < ignore_codes)\n            b->select(b->value(), 0);\n      }\n   }\n}\n\nvoid *FltkListResource::newItem (const char *str, bool enabled, bool selected)\n{\n   Fl_Browser *b = (Fl_Browser *) widget;\n   int index = b->size() + 1;\n   char *label = (char *)malloc(strlen(str) + 1 + currDepth + 4),\n        *s = label;\n\n   memset(s, '\\a', currDepth);\n   s += currDepth;\n   if (!enabled) {\n      // FL_INACTIVE_COLOR\n      *s++ = '@';\n      *s++ = 'N';\n   }\n   // ignore further '@' chars\n   *s++ = '@';\n   *s++ = '.';\n\n   strcpy(s, str);\n\n   b->add(label);\n   free(label);\n\n   if (selected) {\n      b->select(index, selected);\n      if (b->type() == FL_HOLD_BROWSER) {\n         /* Left to its own devices, it sometimes has some suboptimal ideas\n          * about how to scroll, and sometimes doesn't seem to show everything\n          * where it thinks it is.\n          */\n         if (index > showRows) {\n            /* bottomline() and middleline() don't work because the widget is\n             * too tiny at this point for the bbox() call in\n             * Fl_Browser::lineposition() to do what one would want.\n             */\n            b->topline(index - showRows + 1);\n         } else {\n            b->topline(1);\n         }\n      }\n   }\n   queueResize (true);\n   return NULL;\n}\n\nvoid FltkListResource::addItem (const char *str, bool enabled, bool selected)\n{\n   // Fl_Browser_::incr_height() for item height won't do the right thing if\n   // the first item doesn't have anything to it.\n   if (!str || !*str)\n      str = \" \";\n   newItem(str, enabled, selected);\n}\n\nvoid FltkListResource::setItem (int index, bool selected)\n{\n   Fl_Browser *b = (Fl_Browser *) widget;\n\n   b->select(index + 1, selected);\n}\n\nvoid FltkListResource::pushGroup (const char *name, bool enabled)\n{\n   bool en = false;\n   bool selected = false;\n\n   // Fl_Browser_::incr_height() for item height won't do the right thing if\n   // the first item doesn't have anything to it.\n   if (!name || !*name)\n      name = \" \";\n\n   // TODO: Proper disabling of item groups\n   newItem(name, en, selected);\n\n   if (currDepth < 3)\n      currDepth++;\n}\n\nvoid FltkListResource::popGroup ()\n{\n   CustBrowser *b = (CustBrowser *) widget;\n\n   newItem(\" \", false, false);\n   b->hide(b->size());\n\n   if (currDepth)\n      currDepth--;\n}\n\nint FltkListResource::getMaxItemWidth()\n{\n   return ((CustBrowser *) widget)->full_width();\n}\n\nvoid FltkListResource::sizeRequest (core::Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequest\");\n\n   if (style) {\n      CustBrowser *b = (CustBrowser *) widget;\n      int height = b->full_height();\n      requisition->width = getMaxItemWidth() + 4;\n\n      if (showRows * b->avg_height() < height) {\n         height = showRows * b->avg_height();\n         b->has_scrollbar(Fl_Browser_::VERTICAL_ALWAYS);\n         requisition->width += Fl::scrollbar_size();\n      } else {\n         b->has_scrollbar(0);\n      }\n\n      requisition->descent = style->font->descent + 2;\n      requisition->ascent = height - style->font->descent + 2;\n   } else {\n      requisition->width = 1;\n      requisition->ascent = 1;\n      requisition->descent = 0;\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\nint FltkListResource::getNumberOfItems()\n{\n   return ((Fl_Browser*)widget)->size();\n}\n\nbool FltkListResource::isSelected (int index)\n{\n   Fl_Browser *b = (Fl_Browser *) widget;\n\n   return b->selected(index + 1) ? true : false;\n}\n\n} // namespace ui\n} // namespace fltk\n} // namespace dw\n\n"
  },
  {
    "path": "dw/fltkui.hh",
    "content": "#ifndef __DW_FLTK_UI_HH__\n#define __DW_FLTK_UI_HH__\n\n#ifndef __INCLUDED_FROM_DW_FLTK_CORE_HH__\n#   error Do not include this file directly, use \"fltkcore.hh\" instead.\n#endif\n\n#include <FL/Fl_Button.H>\n#include <FL/Fl_Menu.H>\n#include <FL/Fl_Text_Buffer.H>\n\nnamespace dw {\nnamespace fltk {\n\n/**\n * \\brief FLTK implementation of dw::core::ui.\n *\n * <div style=\"border: 2px solid #ff0000; margin-top: 0.5em;\n * margin-bottom: 0.5em; padding: 0.5em 1em;\n * background-color: #ffefe0\"><b>Update:</b> The complicated design\n * results from my insufficient knowledge of C++ some years ago; since\n * then, I've learned how to deal with \"diamond inheritance\", as the\n * (ideal, not actually implemented) design in the first diagram\n * shows. It should be possible to implement this ideal design in a\n * straightforward way, and so get rid of templates. --SG</div>\n *\n * The design should be like this:\n *\n * \\dot\n * digraph G {\n *    node [shape=record, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n *          labelfontname=Helvetica, labelfontsize=10, color=\"#404040\",\n *          labelfontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *\n *    subgraph cluster_core {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"dw::core::ui\";\n *\n *       Resource [color=\"#a0a0a0\", URL=\"\\ref dw::core::ui::Resource\"];\n *       LabelButtonResource [color=\"#a0a0a0\",\n *                            URL=\"\\ref dw::core::ui::LabelButtonResource\"];\n *       EntryResource [color=\"#a0a0a0\",\n *                      URL=\"\\ref dw::core::ui::EntryResource\"];\n *    }\n *\n *    subgraph cluster_fltk {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"dw::fltk::ui\";\n *\n *       FltkResource [color=\"#a0a0a0\", URL=\"\\ref dw::fltk::ui::FltkResource\"];\n *       FltkLabelButtonResource\n *          [URL=\"\\ref dw::fltk::ui::FltkLabelButtonResource\"];\n *       FltkEntryResource [URL=\"\\ref dw::fltk::ui::FltkEntryResource\"];\n *    }\n *\n *    Resource -> LabelButtonResource;\n *    Resource -> EntryResource;\n *    FltkResource -> FltkLabelButtonResource;\n *    FltkResource -> FltkEntryResource;\n *    Resource -> FltkResource;\n *    LabelButtonResource -> FltkLabelButtonResource;\n *    EntryResource -> FltkEntryResource;\n * }\n * \\enddot\n *\n * <center>[\\ref uml-legend \"legend\"]</center>\n *\n * where dw::fltk::ui::FltkResource provides some base funtionality for all\n * conctrete FLTK implementations of sub-interfaces of dw::core::ui::Resource.\n * However, this is not directly possible in C++, since the base class\n * dw::core::ui::Resource is ambiguous for\n * dw::fltk::ui::FltkLabelButtonResource.\n *\n * To solve this, we have to remove the dependency between\n * dw::fltk::ui::FltkResource and dw::core::ui::Resource, instead, the part\n * of dw::core::ui::Resource, which is implemented in\n * dw::fltk::ui::FltkResource, must be explicitly delegated from\n * dw::fltk::ui::FltkLabelButtonResourceto dw::fltk::ui::FltkResource:\n *\n * \\dot\n * digraph G {\n *    node [shape=record, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n *          labelfontname=Helvetica, labelfontsize=10, color=\"#404040\",\n *          labelfontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *\n *    subgraph cluster_core {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"dw::core::ui\";\n *\n *       Resource [color=\"#a0a0a0\", URL=\"\\ref dw::core::ui::Resource\"];\n *       LabelButtonResource [color=\"#a0a0a0\",\n *                           URL=\"\\ref dw::core::ui::LabelButtonResource\"];\n *       EntryResource [color=\"#a0a0a0\",\n *                      URL=\"\\ref dw::core::ui::EntryResource\"];\n *    }\n *\n *    subgraph cluster_fltk {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"dw::fltk::ui\";\n *\n *       FltkResource [color=\"#a0a0a0\", URL=\"\\ref dw::fltk::ui::FltkResource\"];\n *       FltkLabelButtonResource\n *          [URL=\"\\ref dw::fltk::ui::FltkLabelButtonResource\"];\n *       FltkEntryResource [URL=\"\\ref dw::fltk::ui::FltkEntryResource\"];\n *    }\n *\n *    Resource -> LabelButtonResource;\n *    Resource -> EntryResource;\n *    FltkResource -> FltkLabelButtonResource;\n *    FltkResource -> FltkEntryResource;\n *    LabelButtonResource -> FltkLabelButtonResource;\n *    EntryResource -> FltkEntryResource;\n * }\n * \\enddot\n *\n * <center>[\\ref uml-legend \"legend\"]</center>\n *\n * To make this a bit simpler, we use templates:\n *\n * \\dot\n * digraph G {\n *    node [shape=record, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n *          labelfontname=Helvetica, labelfontsize=10, color=\"#404040\",\n *          labelfontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *\n *    subgraph cluster_core {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"dw::core::ui\";\n *\n *       Resource [color=\"#a0a0a0\", URL=\"\\ref dw::core::ui::Resource\"];\n *       LabelButtonResource [color=\"#a0a0a0\",\n *                            URL=\"\\ref dw::core::ui::LabelButtonResource\"];\n *       EntryResource [color=\"#a0a0a0\",\n *                      URL=\"\\ref dw::core::ui::EntryResource\"];\n *    }\n *\n *    subgraph cluster_fltk {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"dw::fltk::ui\";\n *\n *       FltkResource [color=\"#a0a0a0\", URL=\"\\ref dw::fltk::ui::FltkResource\"];\n *       FltkSpecificResource [color=\"#a0a0a0\",\n *                             fillcolor=\"#ffffc0\", style=\"filled\"\n *                             URL=\"\\ref dw::fltk::ui::FltkSpecificResource\"];\n *       FltkSpecificResource_button [color=\"#a0a0a0\",\n *                       label=\"FltkSpecificResource \\<LabelButtonResource\\>\"];\n *       FltkSpecificResource_entry [color=\"#a0a0a0\",\n *                             label=\"FltkSpecificResource \\<EntryResource\\>\"];\n *       FltkEntryResource [URL=\"\\ref dw::fltk::ui::FltkEntryResource\"];\n *       FltkLabelButtonResource\n *          [URL=\"\\ref dw::fltk::ui::FltkLabelButtonResource\"];\n *    }\n *\n *    Resource -> LabelButtonResource;\n *    Resource -> EntryResource;\n *    FltkResource -> FltkSpecificResource;\n *    FltkSpecificResource -> FltkSpecificResource_button [arrowhead=\"open\",\n *                                                         arrowtail=\"none\",\n *                                                         dir=\"both\",\n *                                                         style=\"dashed\",\n *                                                         color=\"#808000\"];\n *    FltkSpecificResource -> FltkSpecificResource_entry [arrowhead=\"open\",\n *                                                        arrowtail=\"none\",\n *                                                        dir=\"both\",\n *                                                        style=\"dashed\",\n *                                                        color=\"#808000\"];\n *    LabelButtonResource -> FltkSpecificResource_button;\n *    EntryResource -> FltkSpecificResource_entry;\n *    FltkSpecificResource_button -> FltkLabelButtonResource;\n *    FltkSpecificResource_entry -> FltkEntryResource;\n * }\n * \\enddot\n *\n * <center>[\\ref uml-legend \"legend\"]</center>\n */\nnamespace ui {\n\n/**\n * ...\n */\nclass FltkResource: public lout::object::Object\n{\nprivate:\n   bool enabled;\n\nprotected:\n   FltkView *view;\n   Fl_Widget *widget;\n   core::Allocation allocation;\n   FltkPlatform *platform;\n\n   core::style::Style *style;\n\n   FltkResource (FltkPlatform *platform);\n   void init (FltkPlatform *platform);\n   virtual Fl_Widget *createNewWidget (core::Allocation *allocation) = 0;\n\n   virtual void setWidgetStyle (Fl_Widget *widget, core::style::Style *style);\n   void setDisplayed (bool displayed);\n   bool displayed();\npublic:\n   ~FltkResource ();\n\n   virtual void attachView (FltkView *view);\n   virtual void detachView (FltkView *view);\n\n   void sizeAllocate (core::Allocation *allocation);\n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n\n   void setStyle (core::style::Style *style);\n\n   bool isEnabled ();\n   void setEnabled (bool enabled);\n};\n\n\ntemplate <class I> class FltkSpecificResource: public I, public FltkResource\n{\npublic:\n   FltkSpecificResource (FltkPlatform *platform);\n   ~FltkSpecificResource ();\n\n   void sizeAllocate (core::Allocation *allocation);\n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n   void setStyle (core::style::Style *style);\n\n   bool isEnabled ();\n   void setEnabled (bool enabled);\n};\n\n\nclass FltkLabelButtonResource:\n   public FltkSpecificResource <dw::core::ui::LabelButtonResource>\n{\nprivate:\n   const char *label;\n\n   static void widgetCallback (Fl_Widget *widget, void *data);\n\nprotected:\n   Fl_Widget *createNewWidget (core::Allocation *allocation);\n\npublic:\n   FltkLabelButtonResource (FltkPlatform *platform, const char *label);\n   ~FltkLabelButtonResource ();\n\n   void sizeRequest (core::Requisition *requisition);\n\n   const char *getLabel ();\n   void setLabel (const char *label);\n};\n\n\nclass FltkComplexButtonResource:\n   public FltkSpecificResource <dw::core::ui::ComplexButtonResource>\n{\nprivate:\n   bool relief;\n\n   static void widgetCallback (Fl_Widget *widget, void *data);\n\nprotected:\n   FltkView *topView, *flatView;\n\n   void attachView (FltkView *view);\n   void detachView (FltkView *view);\n\n   void sizeAllocate (core::Allocation *allocation);\n\n   dw::core::Platform *createPlatform ();\n   void setLayout (dw::core::Layout *layout);\n\n   int reliefXThickness ();\n   int reliefYThickness ();\n\n   Fl_Widget *createNewWidget (core::Allocation *allocation);\n\npublic:\n   FltkComplexButtonResource (FltkPlatform *platform, dw::core::Widget *widget,\n                              bool relief);\n   ~FltkComplexButtonResource ();\n};\n\n\n/**\n * \\bug Maximal length not supported yet.\n * \\todo Text values are not synchronized (not needed in dillo).\n */\nclass FltkEntryResource:\n   public FltkSpecificResource <dw::core::ui::EntryResource>\n{\nprivate:\n   int size;\n   bool password;\n   const char *initText;\n   char *label;\n   int label_w;\n   char *placeholder;\n   bool editable;\n\n   static void widgetCallback (Fl_Widget *widget, void *data);\n   void setDisplayed (bool displayed);\n\nprotected:\n   Fl_Widget *createNewWidget (core::Allocation *allocation);\n   void setWidgetStyle (Fl_Widget *widget, core::style::Style *style);\n\npublic:\n   FltkEntryResource (FltkPlatform *platform, int size, bool password,\n                      const char *label, const char *placeholder);\n   ~FltkEntryResource ();\n\n   void sizeRequest (core::Requisition *requisition);\n   void sizeAllocate (core::Allocation *allocation);\n\n   const char *getText ();\n   void setText (const char *text);\n   bool isEditable ();\n   void setEditable (bool editable);\n   void setMaxLength (int maxlen);\n};\n\n\nclass FltkMultiLineTextResource:\n   public FltkSpecificResource <dw::core::ui::MultiLineTextResource>\n{\nprivate:\n   bool editable;\n   int numCols, numRows;\n   char *placeholder;\nprotected:\n   Fl_Widget *createNewWidget (core::Allocation *allocation);\n   void setWidgetStyle (Fl_Widget *widget, core::style::Style *style);\n\npublic:\n   FltkMultiLineTextResource (FltkPlatform *platform, int cols, int rows,\n                              const char *placeholder);\n   ~FltkMultiLineTextResource ();\n\n   void sizeRequest (core::Requisition *requisition);\n\n   const char *getText ();\n   void setText (const char *text);\n   bool isEditable ();\n   void setEditable (bool editable);\n};\n\n\ntemplate <class I> class FltkToggleButtonResource:\n   public FltkSpecificResource <I>\n{\nprivate:\n   bool initActivated;\n\nprotected:\n   virtual Fl_Button *createNewButton (core::Allocation *allocation) = 0;\n   Fl_Widget *createNewWidget (core::Allocation *allocation);\n   void setWidgetStyle (Fl_Widget *widget, core::style::Style *style);\n\npublic:\n   FltkToggleButtonResource (FltkPlatform *platform,\n                             bool activated);\n   ~FltkToggleButtonResource ();\n\n   void sizeRequest (core::Requisition *requisition);\n\n   bool isActivated ();\n   void setActivated (bool activated);\n};\n\n\nclass FltkCheckButtonResource:\n   public FltkToggleButtonResource <dw::core::ui::CheckButtonResource>\n{\nprotected:\n   Fl_Button *createNewButton (core::Allocation *allocation);\n\npublic:\n   FltkCheckButtonResource (FltkPlatform *platform,\n                            bool activated);\n   ~FltkCheckButtonResource ();\n};\n\n\nclass FltkRadioButtonResource:\n   public FltkToggleButtonResource <dw::core::ui::RadioButtonResource>\n{\nprivate:\n   class Group\n   {\n   private:\n      class FltkGroupIterator:\n         public dw::core::ui::RadioButtonResource::GroupIterator\n      {\n      private:\n         lout::container::typed::Iterator <FltkRadioButtonResource> it;\n\n      public:\n         inline FltkGroupIterator (lout::container::typed::List\n                                   <FltkRadioButtonResource>\n                                   *list)\n            { it = list->iterator (); }\n\n         bool hasNext ();\n         dw::core::ui::RadioButtonResource *getNext ();\n         void unref ();\n      };\n\n      lout::container::typed::List <FltkRadioButtonResource> *list;\n\n   protected:\n      ~Group ();\n\n   public:\n      Group (FltkRadioButtonResource *radioButtonResource);\n\n      inline lout::container::typed::Iterator <FltkRadioButtonResource>\n                                              iterator ()\n      {\n         return list->iterator ();\n      }\n\n      inline dw::core::ui::RadioButtonResource::GroupIterator\n         *groupIterator ()\n      {\n         return new FltkGroupIterator (list);\n      }\n\n      void connect (FltkRadioButtonResource *radioButtonResource);\n      void unconnect (FltkRadioButtonResource *radioButtonResource);\n   };\n\n   Group *group;\n\n   static void widgetCallback (Fl_Widget *widget, void *data);\n   void buttonClicked ();\n\nprotected:\n   Fl_Button *createNewButton (core::Allocation *allocation);\n\npublic:\n   FltkRadioButtonResource (FltkPlatform *platform,\n                            FltkRadioButtonResource *groupedWith,\n                            bool activated);\n   ~FltkRadioButtonResource ();\n\n   GroupIterator *groupIterator ();\n};\n\n\ntemplate <class I> class FltkSelectionResource:\n   public FltkSpecificResource <I>\n{\nprotected:\n   virtual bool setSelectedItems() { return false; }\n   virtual void addItem (const char *str, bool enabled, bool selected) = 0;\n   virtual void setItem (int index, bool selected) = 0;\n   virtual void pushGroup (const char *name, bool enabled) = 0;\n   virtual void popGroup () = 0;\npublic:\n   FltkSelectionResource (FltkPlatform *platform) :\n      FltkSpecificResource<I> (platform) {};\n   dw::core::Iterator *iterator (dw::core::Content::Type mask, bool atEnd);\n};\n\n\nclass FltkOptionMenuResource:\n   public FltkSelectionResource <dw::core::ui::OptionMenuResource>\n{\nprotected:\n   Fl_Widget *createNewWidget (core::Allocation *allocation);\n   virtual bool setSelectedItems() { return true; }\n   void setWidgetStyle (Fl_Widget *widget, core::style::Style *style);\n   int getNumberOfItems();\n   int getMaxItemWidth ();\nprivate:\n   static void widgetCallback (Fl_Widget *widget, void *data);\n   void enlargeMenu();\n   Fl_Menu_Item *newItem();\n   Fl_Menu_Item *menu;\n   int itemsAllocated, itemsUsed;\npublic:\n   FltkOptionMenuResource (FltkPlatform *platform);\n   ~FltkOptionMenuResource ();\n\n   void addItem (const char *str, bool enabled, bool selected);\n   void setItem (int index, bool selected);\n   void pushGroup (const char *name, bool enabled);\n   void popGroup ();\n\n   void sizeRequest (core::Requisition *requisition);\n   bool isSelected (int index);\n};\n\nclass FltkListResource:\n   public FltkSelectionResource <dw::core::ui::ListResource>\n{\nprotected:\n   Fl_Widget *createNewWidget (core::Allocation *allocation);\n   void setWidgetStyle (Fl_Widget *widget, core::style::Style *style);\n   int getNumberOfItems();\n   int getMaxItemWidth ();\nprivate:\n   static void widgetCallback (Fl_Widget *widget, void *data);\n   void *newItem (const char *str, bool enabled, bool selected);\n   int currDepth;\n   int colWidths[4];\n   int showRows;\n   ListResource::SelectionMode mode;\npublic:\n   FltkListResource (FltkPlatform *platform,\n                     core::ui::ListResource::SelectionMode selectionMode,\n                     int rows);\n   ~FltkListResource ();\n\n   void addItem (const char *str, bool enabled, bool selected);\n   void setItem (int index, bool selected);\n   void pushGroup (const char *name, bool enabled);\n   void popGroup ();\n\n   void sizeRequest (core::Requisition *requisition);\n   bool isSelected (int index);\n};\n\n\n} // namespace ui\n} // namespace fltk\n} // namespace dw\n\n\n#endif // __DW_FLTK_UI_HH__\n"
  },
  {
    "path": "dw/fltkviewbase.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"fltkviewport.hh\"\n\n#include <FL/Fl.H>\n#include <FL/fl_draw.H>\n\n#include <stdio.h>\n#include \"../lout/msg.h\"\n\nusing namespace lout::object;\nusing namespace lout::container::typed;\n\nnamespace dw {\nnamespace fltk {\n\nFltkViewBase::BackBuffer::BackBuffer ()\n{\n   w = 0;\n   h = 0;\n   created = false;\n}\n\nFltkViewBase::BackBuffer::~BackBuffer ()\n{\n   if (created)\n      fl_delete_offscreen (offscreen);\n}\n\nvoid FltkViewBase::BackBuffer::setSize (int w, int h)\n{\n   if (!created || w > this->w || h > this->h) {\n      this->w = w;\n      this->h = h;\n      if (created)\n         fl_delete_offscreen (offscreen);\n      offscreen = fl_create_offscreen (w, h);\n      created = true;\n   }\n}\n\nFltkViewBase::BackBuffer *FltkViewBase::backBuffer;\nbool FltkViewBase::backBufferInUse;\n\nFltkViewBase::FltkViewBase (int x, int y, int w, int h, const char *label):\n   Fl_Group (x, y, w, h, label)\n{\n   Fl_Group::current(0);\n   canvasWidth = 1;\n   canvasHeight = 1;\n   bgColor = FL_WHITE;\n   mouse_x = mouse_y = 0;\n   focused_child = NULL;\n   exposeArea = NULL;\n   if (backBuffer == NULL) {\n      backBuffer = new BackBuffer ();\n   }\n   box(FL_NO_BOX);\n   resizable(NULL);\n}\n\nFltkViewBase::~FltkViewBase ()\n{\n   cancelQueueDraw ();\n}\n\nvoid FltkViewBase::setBufferedDrawing (bool b) {\n   if (b && backBuffer == NULL) {\n      backBuffer = new BackBuffer ();\n   } else if (!b && backBuffer != NULL) {\n      delete backBuffer;\n      backBuffer = NULL;\n   }\n}\n\nvoid FltkViewBase::draw ()\n{\n   int d = damage ();\n\n   if ((d & FL_DAMAGE_USER1) && !(d & FL_DAMAGE_EXPOSE)) {\n      lout::container::typed::Iterator <core::Rectangle> it;\n\n      for (it = drawRegion.rectangles (); it.hasNext (); ) {\n         draw (it.getNext (), DRAW_BUFFERED);\n      }\n\n      drawRegion.clear ();\n      d &= ~FL_DAMAGE_USER1;\n   }\n\n   if (d & FL_DAMAGE_CHILD) {\n      drawChildWidgets ();\n      d &= ~FL_DAMAGE_CHILD;\n   }\n\n   if (d) {\n      dw::core::Rectangle rect (\n         translateViewXToCanvasX (x ()),\n         translateViewYToCanvasY (y ()),\n         w (),\n         h ());\n\n      if (d == FL_DAMAGE_SCROLL) {\n         // a clipping rectangle has already been set by fltk::scrollrect ()\n         draw (&rect, DRAW_PLAIN);\n      } else {\n         draw (&rect, DRAW_CLIPPED);\n         drawRegion.clear ();\n      }\n   }\n}\n\nvoid FltkViewBase::draw (const core::Rectangle *rect,\n                         DrawType type)\n{\n   int X = translateCanvasXToViewX (rect->x);\n   int Y = translateCanvasYToViewY (rect->y);\n   int W, H;\n\n   // fl_clip_box() can't handle values greater than SHRT_MAX!\n   if (X > x () + w () || Y > y () + h ())\n      return;\n\n   W = X + rect->width > x () + w () ? x () + w () - X : rect->width;\n   H = Y + rect->height > y () + h () ? y () + h () - Y : rect->height;\n\n   fl_clip_box(X, Y, W, H, X, Y, W, H);\n\n   core::Rectangle r (translateViewXToCanvasX (X),\n                      translateViewYToCanvasY (Y), W, H);\n\n   if (r.isEmpty ())\n      return;\n\n   exposeArea = &r;\n\n   if (type == DRAW_BUFFERED && backBuffer && !backBufferInUse) {\n      backBufferInUse = true;\n      backBuffer->setSize (X + W, Y + H); // would be nicer to use (W, H)...\n      fl_begin_offscreen (backBuffer->offscreen);\n      fl_push_matrix ();\n      fl_color (bgColor);\n      fl_rectf (X, Y, W, H);\n      theLayout->expose (this, &r);\n      fl_pop_matrix ();\n      fl_end_offscreen ();\n      fl_copy_offscreen (X, Y, W, H, backBuffer->offscreen, X, Y);\n      backBufferInUse = false;\n   } else if (type == DRAW_BUFFERED || type == DRAW_CLIPPED) {\n      // if type == DRAW_BUFFERED but we do not have backBuffer available\n      // we fall back to clipped drawing\n      fl_push_clip (X, Y, W, H);\n      fl_color (bgColor);\n      fl_rectf (X, Y, W, H);\n      theLayout->expose (this, &r);\n      fl_pop_clip ();\n   } else {\n      fl_color (bgColor);\n      fl_rectf (X, Y, W, H);\n      theLayout->expose (this, &r);\n   }\n   // DEBUG:\n   //fl_color(FL_RED);\n   //fl_rect(X, Y, W, H);\n\n   exposeArea = NULL;\n}\n\nvoid FltkViewBase::drawChildWidgets () {\n   for (int i = children () - 1; i >= 0; i--) {\n      Fl_Widget& w = *child(i);\n#if 0\nPORT1.3\n      if (w.damage() & DAMAGE_CHILD_LABEL) {\n         draw_outside_label(w);\n         w.set_damage(w.damage() & ~DAMAGE_CHILD_LABEL);\n      }\n#endif\n      update_child(w);\n   }\n}\n\ncore::ButtonState getDwButtonState ()\n{\n   int s1 = Fl::event_state ();\n   int s2 = (core::ButtonState)0;\n\n   if (s1 & FL_SHIFT)   s2 |= core::SHIFT_MASK;\n   if (s1 & FL_CTRL)    s2 |= core::CONTROL_MASK;\n   if (s1 & FL_ALT)     s2 |= core::META_MASK;\n   if (s1 & FL_BUTTON1) s2 |= core::BUTTON1_MASK;\n   if (s1 & FL_BUTTON2) s2 |= core::BUTTON2_MASK;\n   if (s1 & FL_BUTTON3) s2 |= core::BUTTON3_MASK;\n\n   return (core::ButtonState)s2;\n}\n\n/*\n * We handle Tab to determine which FLTK widget should get focus.\n *\n * Presumably a proper solution that allows focusing links, etc., would live\n * in Textblock and use iterators.\n */\nint FltkViewBase::manageTabToFocus()\n{\n   int i, ret = 0;\n   Fl_Widget *old_child = NULL;\n\n   if (this == Fl::focus()) {\n      // if we have focus, give it to a child. Go forward typically,\n      // or backward with Shift pressed.\n      if (!(Fl::event_state() & FL_SHIFT)) {\n         for (i = 0; i < children(); i++) {\n            if (child(i)->take_focus()) {\n               ret = 1;\n               break;\n            }\n         }\n      } else {\n         for (i = children() - 1; i >= 0; i--) {\n            if (child(i)->take_focus()) {\n               ret = 1;\n               break;\n            }\n         }\n      }\n   } else {\n      // tabbing between children\n      old_child = Fl::focus();\n\n      if (!(ret = Fl_Group::handle (FL_KEYBOARD))) {\n         // group didn't have any more children to focus.\n         Fl::focus(this);\n         return 1;\n      } else {\n         // which one did it focus? (Note i == children() if not found)\n         i = find(Fl::focus());\n      }\n   }\n   if (ret) {\n      if (i >= 0 && i < children()) {\n         Fl_Widget *c = child(i);\n         int canvasX = translateViewXToCanvasX(c->x()),\n             canvasY = translateViewYToCanvasY(c->y());\n\n         theLayout->scrollTo(core::HPOS_INTO_VIEW, core::VPOS_INTO_VIEW,\n                             canvasX, canvasY, c->w(), c->h());\n\n         // Draw the children who gained and lost focus. Otherwise a\n         // widget that had been only partly visible still shows its old\n         // appearance in the previously-visible portion.\n         core::Rectangle r(canvasX, canvasY, c->w(), c->h());\n\n         queueDraw(&r);\n\n         if (old_child) {\n            r.x = translateViewXToCanvasX(old_child->x());\n            r.y = translateViewYToCanvasY(old_child->y());\n            r.width = old_child->w();\n            r.height = old_child->h();\n            queueDraw(&r);\n         }\n      }\n   }\n   return ret;\n}\n\nint FltkViewBase::handle (int event)\n{\n   bool processed;\n\n   /**\n    * \\todo Consider, whether this from the FLTK documentation has any\n    *    impacts: \"To receive fltk::RELEASE events you must return non-zero\n    *    when passed a fltk::PUSH event. \"\n    */\n   switch(event) {\n   case FL_PUSH:\n      /* Hide the tooltip */\n      theLayout->cancelTooltip();\n\n      processed =\n         theLayout->buttonPress (this, Fl::event_clicks () + 1,\n                                 translateViewXToCanvasX (Fl::event_x ()),\n                                 translateViewYToCanvasY (Fl::event_y ()),\n                                 getDwButtonState (), Fl::event_button ());\n      _MSG(\"PUSH => %s\\n\", processed ? \"true\" : \"false\");\n      if (processed) {\n         /* pressed dw content; give focus to the view */\n         if (Fl::event_button() != FL_RIGHT_MOUSE)\n            Fl::focus(this);\n         return true;\n      }\n      break;\n   case FL_RELEASE:\n      processed =\n         theLayout->buttonRelease (this, Fl::event_clicks () + 1,\n                                   translateViewXToCanvasX (Fl::event_x ()),\n                                   translateViewYToCanvasY (Fl::event_y ()),\n                                   getDwButtonState (), Fl::event_button ());\n      _MSG(\"RELEASE => %s\\n\", processed ? \"true\" : \"false\");\n      if (processed)\n         return true;\n      break;\n   case FL_MOVE:\n      mouse_x = Fl::event_x();\n      mouse_y = Fl::event_y();\n      processed =\n         theLayout->motionNotify (this,\n                                  translateViewXToCanvasX (mouse_x),\n                                  translateViewYToCanvasY (mouse_y),\n                                  getDwButtonState ());\n      _MSG(\"MOVE => %s\\n\", processed ? \"true\" : \"false\");\n      if (processed)\n         return true;\n      break;\n   case FL_DRAG:\n      processed =\n         theLayout->motionNotify (this,\n                                  translateViewXToCanvasX (Fl::event_x ()),\n                                  translateViewYToCanvasY (Fl::event_y ()),\n                                  getDwButtonState ());\n      _MSG(\"DRAG => %s\\n\", processed ? \"true\" : \"false\");\n      if (processed)\n         return true;\n      break;\n   case FL_ENTER:\n      theLayout->enterNotify (this,\n                              translateViewXToCanvasX (Fl::event_x ()),\n                              translateViewYToCanvasY (Fl::event_y ()),\n                              getDwButtonState ());\n      break;\n   case FL_HIDE:\n      /* WORKAROUND: strangely, the tooltip window is not automatically hidden\n       * with its parent. Here we fake a LEAVE to achieve it. */\n   case FL_LEAVE:\n      theLayout->leaveNotify (this, getDwButtonState ());\n      break;\n   case FL_FOCUS:\n      if (focused_child && find(focused_child) < children()) {\n         /* strangely, find() == children() if the child is not found */\n         focused_child->take_focus();\n      }\n      return 1;\n   case FL_UNFOCUS:\n      // FLTK delivers UNFOCUS to the previously focused widget\n      if (find(Fl::focus()) < children())\n         focused_child = Fl::focus(); // remember the focused child!\n      else if (Fl::focus() == this)\n         focused_child = NULL; // no focused child this time\n      return 0;\n   case FL_KEYBOARD:\n      if (Fl::event_key() == FL_Tab)\n         return manageTabToFocus();\n      break;\n   default:\n      break;\n   }\n   return Fl_Group::handle (event);\n}\n\n// ----------------------------------------------------------------------\n\nvoid FltkViewBase::setLayout (core::Layout *layout)\n{\n   theLayout = layout;\n   if (usesViewport())\n      theLayout->viewportSizeChanged(this, w(), h());\n}\n\nvoid FltkViewBase::setCanvasSize (int width, int ascent, int descent)\n{\n   canvasWidth = width;\n   canvasHeight = ascent + descent;\n}\n\nvoid FltkViewBase::setCursor (core::style::Cursor cursor)\n{\n   static Fl_Cursor mapDwToFltk[] = {\n      FL_CURSOR_CROSS,\n      FL_CURSOR_DEFAULT,\n      FL_CURSOR_HAND,\n      FL_CURSOR_MOVE,\n      FL_CURSOR_WE,\n      FL_CURSOR_NESW,\n      FL_CURSOR_NWSE,\n      FL_CURSOR_NS,\n      FL_CURSOR_NWSE,\n      FL_CURSOR_NESW,\n      FL_CURSOR_NS,\n      FL_CURSOR_WE,\n      FL_CURSOR_INSERT,\n      FL_CURSOR_WAIT,\n      FL_CURSOR_HELP\n   };\n\n   fl_cursor (mapDwToFltk[cursor]);\n}\n\nvoid FltkViewBase::setBgColor (core::style::Color *color)\n{\n   bgColor = color ?\n      ((FltkColor*)color)->colors[dw::core::style::Color::SHADING_NORMAL] :\n      FL_WHITE;\n}\n\nvoid FltkViewBase::startDrawing (core::Rectangle *area)\n{\n}\n\nvoid FltkViewBase::finishDrawing (core::Rectangle *area)\n{\n}\n\nvoid FltkViewBase::queueDraw (core::Rectangle *area)\n{\n   drawRegion.addRectangle (area);\n   damage (FL_DAMAGE_USER1);  // USER1 for buffered draw\n}\n\nvoid FltkViewBase::queueDrawTotal ()\n{\n   damage (FL_DAMAGE_EXPOSE);\n}\n\nvoid FltkViewBase::cancelQueueDraw ()\n{\n}\n\nvoid FltkViewBase::drawPoint (core::style::Color *color,\n                              core::style::Color::Shading shading,\n                              int x, int y)\n{\n}\n\nvoid FltkViewBase::drawLine (core::style::Color *color,\n                             core::style::Color::Shading shading,\n                             int x1, int y1, int x2, int y2)\n{\n   fl_color(((FltkColor*)color)->colors[shading]);\n   // we clip with a large border (5000px), as clipping causes artefacts\n   // with non-solid line styles.\n   // However it's still better than no clipping at all.\n   clipPoint (&x1, &y1, 5000);\n   clipPoint (&x2, &y2, 5000);\n   fl_line (translateCanvasXToViewX (x1),\n            translateCanvasYToViewY (y1),\n            translateCanvasXToViewX (x2),\n            translateCanvasYToViewY (y2));\n}\n\nvoid FltkViewBase::drawTypedLine (core::style::Color *color,\n                                  core::style::Color::Shading shading,\n                                  core::style::LineType type, int width,\n                                  int x1, int y1, int x2, int y2)\n{\n   char dashes[3], w, ng, d, gap, len;\n   const int f = 2;\n\n   w = (width == 1) ? 0 : width;\n   if (type == core::style::LINE_DOTTED) {\n      /* customized drawing for dotted lines */\n      len = (x2 == x1) ? y2 - y1 + 1 : (y2 == y1) ? x2 - x1 + 1 : 0;\n      ng = len / f*width;\n      d = len % f*width;\n      gap = ng ? d/ng + (w > 3 ? 2 : 0) : 0;\n      dashes[0] = 1; dashes[1] = f*width-gap; dashes[2] = 0;\n      fl_line_style(FL_DASH + FL_CAP_ROUND, w, dashes);\n\n      /* These formulas also work, but ain't pretty ;)\n       * fl_line_style(FL_DOT + FL_CAP_ROUND, w);\n       * dashes[0] = 1; dashes[1] = 3*width-2; dashes[2] = 0;\n       */\n   } else if (type == core::style::LINE_DASHED) {\n      fl_line_style(FL_DASH + FL_CAP_ROUND, w);\n   }\n\n   fl_color(((FltkColor*)color)->colors[shading]);\n   drawLine (color, shading, x1, y1, x2, y2);\n\n   if (type != core::style::LINE_NORMAL)\n      fl_line_style(FL_SOLID);\n}\n\nvoid FltkViewBase::drawRectangle (core::style::Color *color,\n                                  core::style::Color::Shading shading,\n                                  bool filled,\n                                  int X, int Y, int width, int height)\n{\n   fl_color(((FltkColor*)color)->colors[shading]);\n   if (width < 0) {\n      X += width;\n      width = -width;\n   }\n   if (height < 0) {\n      Y += height;\n      height = -height;\n   }\n\n   int x1 = X;\n   int y1 = Y;\n   int x2 = X + width;\n   int y2 = Y + height;\n\n   // We only support rectangles with line width 1px, so we clip with\n   // a rectangle 1px wider and higher than what we actually expose.\n   // This is only really necessary for non-filled rectangles.\n   clipPoint (&x1, &y1, 1);\n   clipPoint (&x2, &y2, 1);\n\n   x1 = translateCanvasXToViewX (x1);\n   y1 = translateCanvasYToViewY (y1);\n   x2 = translateCanvasXToViewX (x2);\n   y2 = translateCanvasYToViewY (y2);\n\n   if (filled)\n      fl_rectf (x1, y1, x2 - x1, y2 - y1);\n   else\n      fl_rect (x1, y1, x2 - x1, y2 - y1);\n}\n\nvoid FltkViewBase::drawArc (core::style::Color *color,\n                            core::style::Color::Shading shading, bool filled,\n                            int centerX, int centerY, int width, int height,\n                            int angle1, int angle2)\n{\n   fl_color(((FltkColor*)color)->colors[shading]);\n   int x = translateCanvasXToViewX (centerX) - width / 2;\n   int y = translateCanvasYToViewY (centerY) - height / 2;\n\n   fl_arc(x, y, width, height, angle1, angle2);\n   if (filled) {\n      // WORKAROUND: We call both fl_arc and fl_pie due to a FLTK bug\n      // (STR #2703) that was present in 1.3.0.\n      fl_pie(x, y, width, height, angle1, angle2);\n   }\n}\n\nvoid FltkViewBase::drawPolygon (core::style::Color *color,\n                                core::style::Color::Shading shading,\n                                bool filled, bool convex, core::Point *points,\n                                int npoints)\n{\n   if (npoints > 0) {\n      fl_color(((FltkColor*)color)->colors[shading]);\n\n      if (filled) {\n         if (convex)\n            fl_begin_polygon();\n         else\n            fl_begin_complex_polygon();\n      } else\n         fl_begin_loop();\n\n      for (int i = 0; i < npoints; i++) {\n         fl_vertex(translateCanvasXToViewX(points[i].x),\n                   translateCanvasYToViewY(points[i].y));\n      }\n      if (filled) {\n         if (convex)\n            fl_end_polygon();\n         else\n            fl_end_complex_polygon();\n      } else\n         fl_end_loop();\n   }\n}\n\ncore::View *FltkViewBase::getClippingView (int x, int y, int width, int height)\n{\n   fl_push_clip (translateCanvasXToViewX (x), translateCanvasYToViewY (y),\n                 width, height);\n   return this;\n}\n\nvoid FltkViewBase::mergeClippingView (core::View *clippingView)\n{\n   fl_pop_clip ();\n}\n\n// ----------------------------------------------------------------------\n\nFltkWidgetView::FltkWidgetView (int x, int y, int w, int h,\n                                const char *label):\n   FltkViewBase (x, y, w, h, label)\n{\n}\n\nFltkWidgetView::~FltkWidgetView ()\n{\n}\n\nvoid FltkWidgetView::drawText (core::style::Font *font,\n                               core::style::Color *color,\n                               core::style::Color::Shading shading,\n                               int X, int Y, const char *text, int len)\n{\n   //printf (\"drawText (..., %d, %d, '\", X, Y);\n   //for (int i = 0; i < len; i++)\n   //   putchar (text[i]);\n   //printf (\"'\\n\");\n\n   FltkFont *ff = (FltkFont*)font;\n   fl_font(ff->font, ff->size);\n   fl_color(((FltkColor*)color)->colors[shading]);\n\n   if (!font->letterSpacing && !font->fontVariant) {\n      fl_draw(text, len,\n              translateCanvasXToViewX (X), translateCanvasYToViewY (Y));\n   } else {\n      /* Nonzero letter spacing adjustment, draw each glyph individually */\n      int viewX = translateCanvasXToViewX (X),\n          viewY = translateCanvasYToViewY (Y);\n      int curr = 0, next = 0, nb;\n      char chbuf[4];\n      int c, cu, width;\n\n      if (font->fontVariant == core::style::FONT_VARIANT_SMALL_CAPS) {\n         int sc_fontsize = lout::misc::roundInt(ff->size * 0.78);\n         for (curr = 0; next < len; curr = next) {\n            next = theLayout->nextGlyph(text, curr);\n            c = fl_utf8decode(text + curr, text + next, &nb);\n            if ((cu = fl_toupper(c)) == c) {\n               /* already uppercase, just draw the character */\n               fl_font(ff->font, ff->size);\n               width = (int)fl_width(text + curr, next - curr);\n               if (curr && width)\n                  viewX += font->letterSpacing;\n               fl_draw(text + curr, next - curr, viewX, viewY);\n               viewX += width;\n            } else {\n               /* make utf8 string for converted char */\n               nb = fl_utf8encode(cu, chbuf);\n               fl_font(ff->font, sc_fontsize);\n               width = (int)fl_width(chbuf, nb);\n               if (curr && width)\n                  viewX += font->letterSpacing;\n               fl_draw(chbuf, nb, viewX, viewY);\n               viewX += width;\n            }\n         }\n      } else {\n         while (next < len) {\n            next = theLayout->nextGlyph(text, curr);\n            width = (int)fl_width(text + curr, next - curr);\n            if (curr && width)\n               viewX += font->letterSpacing;\n            fl_draw(text + curr, next - curr, viewX, viewY);\n            viewX += width;\n            curr = next;\n         }\n      }\n   }\n}\n\n/*\n * \"simple\" in that it ignores letter-spacing, etc. This was added for image\n * alt text where none of that matters.\n */\nvoid FltkWidgetView::drawSimpleWrappedText (core::style::Font *font,\n                                            core::style::Color *color,\n                                           core::style::Color::Shading shading,\n                                            int X, int Y, int W, int H,\n                                            const char *text)\n{\n   FltkFont *ff = (FltkFont*)font;\n   fl_font(ff->font, ff->size);\n   fl_color(((FltkColor*)color)->colors[shading]);\n   fl_draw(text,\n           translateCanvasXToViewX (X), translateCanvasYToViewY (Y),\n           W, H, FL_ALIGN_TOP|FL_ALIGN_LEFT|FL_ALIGN_WRAP, NULL, 0);\n}\n\nvoid FltkWidgetView::drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,\n                              int X, int Y, int width, int height)\n{\n   ((FltkImgbuf*)imgbuf)->draw (this,\n                                translateCanvasXToViewX (xRoot),\n                                translateCanvasYToViewY (yRoot),\n                                X, Y, width, height);\n}\n\nbool FltkWidgetView::usesFltkWidgets ()\n{\n   return true;\n}\n\nvoid FltkWidgetView::addFltkWidget (Fl_Widget *widget,\n                                    core::Allocation *allocation)\n{\n   allocateFltkWidget (widget, allocation);\n   add (widget);\n}\n\nvoid FltkWidgetView::removeFltkWidget (Fl_Widget *widget)\n{\n   remove (widget);\n}\n\nvoid FltkWidgetView::allocateFltkWidget (Fl_Widget *widget,\n                                       core::Allocation *allocation)\n{\n   widget->resize (translateCanvasXToViewX (allocation->x),\n      translateCanvasYToViewY (allocation->y),\n      allocation->width,\n      allocation->ascent + allocation->descent);\n}\n\nvoid FltkWidgetView::drawFltkWidget (Fl_Widget *widget,\n                                   core::Rectangle *area)\n{\n   draw_child (*widget);\n   draw_outside_label(*widget);\n}\n\n} // namespace fltk\n} // namespace dw\n"
  },
  {
    "path": "dw/fltkviewbase.hh",
    "content": "#ifndef __DW_FLTKVIEWBASE_HH__\n#define __DW_FLTKVIEWBASE_HH__\n\n#include <time.h>         // for time_t\n#include <sys/time.h>     // for time_t in FreeBSD\n\n#include <FL/Fl_Group.H>\n#include <FL/x.H>\n\n#include \"fltkcore.hh\"\n\nnamespace dw {\nnamespace fltk {\n\nclass FltkViewBase: public FltkView, public Fl_Group\n{\nprivate:\n   class BackBuffer {\n      private:\n         int w;\n         int h;\n         bool created;\n\n      public:\n         Fl_Offscreen offscreen;\n\n         BackBuffer ();\n         ~BackBuffer ();\n         void setSize(int w, int h);\n   };\n\n   typedef enum { DRAW_PLAIN, DRAW_CLIPPED, DRAW_BUFFERED } DrawType;\n\n   int bgColor;\n   core::Region drawRegion;\n   core::Rectangle *exposeArea;\n   static BackBuffer *backBuffer;\n   static bool backBufferInUse;\n\n   void draw (const core::Rectangle *rect, DrawType type);\n   void drawChildWidgets ();\n   int manageTabToFocus();\n   inline void clipPoint (int *x, int *y, int border) {\n      if (exposeArea) {\n         if (*x < exposeArea->x - border)\n            *x = exposeArea->x - border;\n         if (*x > exposeArea->x + exposeArea->width + border)\n            *x = exposeArea->x + exposeArea->width + border;\n         if (*y < exposeArea->y - border)\n            *y = exposeArea->y - border;\n         if (*y > exposeArea->y + exposeArea->height + border)\n            *y = exposeArea->y + exposeArea->height + border;\n      }\n   }\nprotected:\n   core::Layout *theLayout;\n   int canvasWidth, canvasHeight;\n   int mouse_x, mouse_y;\n   Fl_Widget *focused_child;\n\n   virtual int translateViewXToCanvasX (int x) = 0;\n   virtual int translateViewYToCanvasY (int y) = 0;\n   virtual int translateCanvasXToViewX (int x) = 0;\n   virtual int translateCanvasYToViewY (int y) = 0;\n\npublic:\n   FltkViewBase (int x, int y, int w, int h, const char *label = 0);\n   ~FltkViewBase ();\n\n   void draw();\n   int handle (int event);\n\n   void setLayout (core::Layout *layout);\n   void setCanvasSize (int width, int ascent, int descent);\n   void setCursor (core::style::Cursor cursor);\n   void setBgColor (core::style::Color *color);\n\n   void startDrawing (core::Rectangle *area);\n   void finishDrawing (core::Rectangle *area);\n   void queueDraw (core::Rectangle *area);\n   void queueDrawTotal ();\n   void cancelQueueDraw ();\n   void drawPoint (core::style::Color *color,\n                   core::style::Color::Shading shading,\n                   int x, int y);\n   void drawLine (core::style::Color *color,\n                  core::style::Color::Shading shading,\n                  int x1, int y1, int x2, int y2);\n   void drawTypedLine (core::style::Color *color,\n                       core::style::Color::Shading shading,\n                       core::style::LineType type, int width,\n                       int x1, int y1, int x2, int y2);\n   void drawRectangle (core::style::Color *color,\n                       core::style::Color::Shading shading, bool filled,\n                       int x, int y, int width, int height);\n   void drawArc (core::style::Color *color,\n                 core::style::Color::Shading shading, bool filled,\n                 int centerX, int centerY, int width, int height,\n                 int angle1, int angle2);\n    void drawPolygon (core::style::Color *color,\n                      core::style::Color::Shading shading,\n                      bool filled, bool convex,\n                      core::Point *points, int npoints);\n\n   core::View *getClippingView (int x, int y, int width, int height);\n   void mergeClippingView (core::View *clippingView);\n   void setBufferedDrawing (bool b);\n};\n\n\nclass FltkWidgetView: public FltkViewBase\n{\npublic:\n   FltkWidgetView (int x, int y, int w, int h, const char *label = 0);\n   ~FltkWidgetView ();\n\n   void drawText (core::style::Font *font,\n                  core::style::Color *color,\n                  core::style::Color::Shading shading,\n                  int x, int y, const char *text, int len);\n   void drawSimpleWrappedText (core::style::Font *font,\n                               core::style::Color *color,\n                               core::style::Color::Shading shading,\n                               int x, int y, int w, int h,\n                               const char *text);\n   void drawImage (core::Imgbuf *imgbuf, int xRoot, int yRoot,\n                   int x, int y, int width, int height);\n\n   bool usesFltkWidgets ();\n   void addFltkWidget (Fl_Widget *widget, core::Allocation *allocation);\n   void removeFltkWidget (Fl_Widget *widget);\n   void allocateFltkWidget (Fl_Widget *widget,\n                            core::Allocation *allocation);\n   void drawFltkWidget (Fl_Widget *widget, core::Rectangle *area);\n};\n\n} // namespace fltk\n} // namespace dw\n\n#endif // __DW_FLTKVIEWBASE_HH__\n\n"
  },
  {
    "path": "dw/fltkviewport.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"fltkviewport.hh\"\n\n#include <FL/Fl.H>\n#include <FL/fl_draw.H>\n#include <FL/names.h>\n\n#include <stdio.h>\n#include \"../lout/msg.h\"\n#include \"../lout/debug.hh\"\n\nusing namespace lout;\nusing namespace lout::object;\nusing namespace lout::container::typed;\n\nnamespace dw {\nnamespace fltk {\n\n/*\n * Lets SHIFT+{Left,Right} go to the parent\n */\nclass CustScrollbar : public Fl_Scrollbar\n{\npublic:\n   CustScrollbar(int x, int y, int w, int h) : Fl_Scrollbar(x,y,w,h) {};\n   int handle(int e) {\n      if (e == FL_SHORTCUT && Fl::event_state() == FL_SHIFT &&\n          (Fl::event_key() == FL_Left || Fl::event_key() == FL_Right))\n         return 0;\n      return Fl_Scrollbar::handle(e);\n   }\n};\n\nFltkViewport::FltkViewport (int X, int Y, int W, int H, const char *label):\n   FltkWidgetView (X, Y, W, H, label)\n{\n   DBG_OBJ_CREATE (\"dw::fltk::FltkViewport\");\n\n   hscrollbar = new CustScrollbar (x (), y (), 1, 1);\n   hscrollbar->type(FL_HORIZONTAL);\n   hscrollbar->callback (hscrollbarCallback, this);\n   hscrollbar->hide();\n   add (hscrollbar);\n\n   vscrollbar = new Fl_Scrollbar (x (), y(), 1, 1);\n   vscrollbar->type(FL_VERTICAL);\n   vscrollbar->callback (vscrollbarCallback, this);\n   vscrollbar->hide();\n   add (vscrollbar);\n\n   hasDragScroll = 1;\n   scrollX = scrollY = scrollDX = scrollDY = 0;\n   horScrolling = verScrolling = dragScrolling = 0;\n\n   gadgetOrientation[0] = GADGET_HORIZONTAL;\n   gadgetOrientation[1] = GADGET_HORIZONTAL;\n   gadgetOrientation[2] = GADGET_VERTICAL;\n   gadgetOrientation[3] = GADGET_HORIZONTAL;\n\n   gadgets =\n      new container::typed::List <object::TypedPointer < Fl_Widget> >\n      (true);\n}\n\nFltkViewport::~FltkViewport ()\n{\n   delete gadgets;\n   DBG_OBJ_DELETE ();\n}\n\nvoid FltkViewport::adjustScrollbarsAndGadgetsAllocation ()\n{\n   int hdiff = 0, vdiff = 0;\n   int visibility = 0;\n\n   _MSG(\" >>FltkViewport::adjustScrollbarsAndGadgetsAllocation\\n\");\n   if (hscrollbar->visible ())\n      visibility |= 1;\n   if (vscrollbar->visible ())\n      visibility |= 2;\n\n   if (gadgets->size () > 0) {\n      switch (gadgetOrientation [visibility]) {\n      case GADGET_VERTICAL:\n         hdiff = SCROLLBAR_THICKNESS;\n         vdiff = SCROLLBAR_THICKNESS * gadgets->size ();\n         break;\n\n      case GADGET_HORIZONTAL:\n         hdiff = SCROLLBAR_THICKNESS * gadgets->size ();\n         vdiff = SCROLLBAR_THICKNESS;\n         break;\n      }\n   } else {\n      hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;\n      vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;\n   }\n\n   hscrollbar->resize(x (), y () + h () - SCROLLBAR_THICKNESS,\n                      w () - hdiff, SCROLLBAR_THICKNESS);\n   vscrollbar->resize(x () + w () - SCROLLBAR_THICKNESS, y (),\n                      SCROLLBAR_THICKNESS, h () - vdiff);\n\n   int X = x () + w () - SCROLLBAR_THICKNESS;\n   int Y = y () + h () - SCROLLBAR_THICKNESS;\n   for (Iterator <TypedPointer < Fl_Widget> > it = gadgets->iterator ();\n        it.hasNext (); ) {\n      Fl_Widget *widget = it.getNext()->getTypedValue ();\n      widget->resize(x (), y (), SCROLLBAR_THICKNESS, SCROLLBAR_THICKNESS);\n\n      switch (gadgetOrientation [visibility]) {\n      case GADGET_VERTICAL:\n         Y -= SCROLLBAR_THICKNESS;\n         break;\n\n      case GADGET_HORIZONTAL:\n         X -= SCROLLBAR_THICKNESS;\n         break;\n      }\n   }\n}\n\nvoid FltkViewport::adjustScrollbarValues ()\n{\n   hscrollbar->value (scrollX, hscrollbar->w (), 0, canvasWidth);\n   vscrollbar->value (scrollY, vscrollbar->h (), 0, canvasHeight);\n}\n\nvoid FltkViewport::hscrollbarChanged ()\n{\n   scroll (hscrollbar->value () - scrollX, 0);\n}\n\nvoid FltkViewport::vscrollbarChanged ()\n{\n   scroll (0, vscrollbar->value () - scrollY);\n}\n\nvoid FltkViewport::vscrollbarCallback (Fl_Widget *vscrollbar,void *viewportPtr)\n{\n   ((FltkViewport*)viewportPtr)->vscrollbarChanged ();\n}\n\nvoid FltkViewport::hscrollbarCallback (Fl_Widget *hscrollbar,void *viewportPtr)\n{\n   ((FltkViewport*)viewportPtr)->hscrollbarChanged ();\n}\n\n// ----------------------------------------------------------------------\n\nvoid FltkViewport::resize(int X, int Y, int W, int H)\n{\n   bool dimension_changed = W != w() || H != h();\n\n   Fl_Group::resize(X, Y, W, H);\n   if (dimension_changed) {\n      theLayout->viewportSizeChanged (this, W, H);\n      adjustScrollbarsAndGadgetsAllocation ();\n   }\n}\n\nvoid FltkViewport::draw_area (void *data, int x, int y, int w, int h)\n{\n  FltkViewport *vp = (FltkViewport*) data;\n  fl_push_clip(x, y, w, h);\n\n  vp->FltkWidgetView::draw ();\n\n  for (Iterator <TypedPointer < Fl_Widget> > it = vp->gadgets->iterator();\n       it.hasNext (); ) {\n     Fl_Widget *widget = it.getNext()->getTypedValue ();\n     vp->draw_child (*widget);\n  }\n\n  fl_pop_clip();\n}\n\n/*\n * Draw the viewport.\n *\n *  + Damage flags come in different ways, draw() should cope with them all.\n *  + Damage flags are alive for visible and hidden widgets.\n *  + FL_DAMAGE_CHILD can flag scroll bars or embedded FLTK widgets.\n */\nvoid FltkViewport::draw ()\n{\n   const int d = damage(),\n             vis_vs = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0,\n             vis_hs = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0,\n             draw = d & (FL_DAMAGE_ALL | FL_DAMAGE_EXPOSE),\n             draw_vs = vis_vs && vscrollbar->damage (),\n             draw_hs = vis_hs && hscrollbar->damage ();\n\n   _MSG(\"FltkViewport::draw d=%d  =>  \", d);\n   // scrollbars\n   if (draw || draw_vs)\n      draw_child (*vscrollbar);\n   if (draw || draw_hs)\n      draw_child (*hscrollbar);\n   if (draw && vis_vs && vis_hs) {\n      fl_color(FL_BACKGROUND_COLOR);\n      fl_rectf(x()+w()-vis_vs, y()+h()-vis_hs, vis_vs, vis_hs);\n   }\n   // main area\n   if (d == FL_DAMAGE_CHILD && (draw_vs || draw_hs)) {\n      _MSG(\"none\\n\");\n   } else if (d == (FL_DAMAGE_SCROLL | FL_DAMAGE_CHILD)) {\n      fl_scroll(x(), y(), w() - vis_vs, h() - vis_hs,\n                -scrollDX, -scrollDY, draw_area, this);\n      _MSG(\"fl_scroll()\\n\");\n   } else {\n      draw_area(this, x(), y(), w() - vis_vs, h() - vis_hs);\n      _MSG(\"draw_area()\\n\");\n   }\n\n   scrollDX = 0;\n   scrollDY = 0;\n}\n\nint FltkViewport::handle (int event)\n{\n   int ret = 0;\n   _MSG(\"FltkViewport::handle %s\\n\", fl_eventnames[event]);\n\n   switch(event) {\n   case FL_KEYBOARD:\n      /* When the viewport has focus (and not one of its children), FLTK\n       * sends the event here. Returning zero tells FLTK to resend the\n       * event as SHORTCUT, which we finally route to the parent. */\n\n      /* As we don't know the exact keybindings set by the user, we ask for\n       * all of them (except for the minimum needed to keep form navigation).*/\n      if (Fl::event_key() != FL_Tab || Fl::event_ctrl())\n         return 0;\n      break;\n\n   case FL_SHORTCUT:\n      /* send it to the parent (UI) */\n      return 0;\n\n   case FL_FOCUS:\n      /** \\bug Draw focus box. */\n      break;\n\n   case FL_UNFOCUS:\n      /** \\bug Undraw focus box. */\n      break;\n\n   case FL_PUSH:\n      if (vscrollbar->visible() && Fl::event_inside(vscrollbar)) {\n         if (vscrollbar->handle(event))\n            verScrolling = 1;\n      } else if (hscrollbar->visible() && Fl::event_inside(hscrollbar)) {\n         if (hscrollbar->handle(event))\n            horScrolling = 1;\n      } else if (FltkWidgetView::handle(event) == 0 &&\n                 Fl::event_button() == FL_MIDDLE_MOUSE) {\n         if (!hasDragScroll) {\n            /* let the parent widget handle it... */\n            return 0;\n         } else {\n            /* receive FL_DRAG and FL_RELEASE */\n            dragScrolling = 1;\n            dragX = Fl::event_x();\n            dragY = Fl::event_y();\n            setCursor (core::style::CURSOR_MOVE);\n         }\n      }\n      return 1;\n      break;\n\n   case FL_DRAG:\n      if (Fl::event_inside(this))\n         Fl::remove_timeout(selectionScroll);\n      if (dragScrolling) {\n         scroll(dragX - Fl::event_x(), dragY - Fl::event_y());\n         dragX = Fl::event_x();\n         dragY = Fl::event_y();\n         return 1;\n      } else if (verScrolling) {\n         vscrollbar->handle(event);\n         return 1;\n      } else if (horScrolling) {\n         hscrollbar->handle(event);\n         return 1;\n      } else if (!Fl::event_inside(this)) {\n         mouse_x = Fl::event_x();\n         mouse_y = Fl::event_y();\n         if (!Fl::has_timeout(selectionScroll, this))\n            Fl::add_timeout(0.025, selectionScroll, this);\n      }\n      break;\n\n   case FL_MOUSEWHEEL:\n      return (Fl::event_dx() ? hscrollbar : vscrollbar)->handle(event);\n      break;\n\n   case FL_RELEASE:\n      Fl::remove_timeout(selectionScroll);\n      if (Fl::event_button() == FL_MIDDLE_MOUSE) {\n         setCursor (core::style::CURSOR_DEFAULT);\n      } else if (verScrolling) {\n         ret = vscrollbar->handle(event);\n      } else if (horScrolling) {\n         ret = hscrollbar->handle(event);\n      }\n      horScrolling = verScrolling = dragScrolling = 0;\n      break;\n\n   case FL_ENTER:\n      /* could be the result of, e.g., closing another window. */\n      mouse_x = Fl::event_x();\n      mouse_y = Fl::event_y();\n      positionChanged();\n      break;\n\n   case FL_LEAVE:\n      mouse_x = mouse_y = -1;\n      break;\n   }\n\n   return ret ? ret : FltkWidgetView::handle (event);\n}\n\n// ----------------------------------------------------------------------\n\nvoid FltkViewport::setCanvasSize (int width, int ascent, int descent)\n{\n   FltkWidgetView::setCanvasSize (width, ascent, descent);\n   adjustScrollbarValues ();\n}\n\n/*\n * This is used to simulate mouse motion (e.g., when scrolling).\n */\nvoid FltkViewport::positionChanged ()\n{\n   if (!dragScrolling && mouse_x >= x() && mouse_x < x()+w() && mouse_y >= y()\n       && mouse_y < y()+h())\n      (void)theLayout->motionNotify (this,\n                                     translateViewXToCanvasX (mouse_x),\n                                     translateViewYToCanvasY (mouse_y),\n                                     (core::ButtonState)0);\n}\n\n/*\n * For scrollbars, this currently sets the same step to both vertical and\n * horizontal. It may be differentiated if necessary.\n */\nvoid FltkViewport::setScrollStep(int step)\n{\n   vscrollbar->linesize(step);\n   hscrollbar->linesize(step);\n}\n\nbool FltkViewport::usesViewport ()\n{\n   return true;\n}\n\nint FltkViewport::getHScrollbarThickness ()\n{\n   return SCROLLBAR_THICKNESS;\n}\n\nint FltkViewport::getVScrollbarThickness ()\n{\n   return SCROLLBAR_THICKNESS;\n}\n\nvoid FltkViewport::scrollTo (int x, int y)\n{\n   int hdiff = vscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;\n   int vdiff = hscrollbar->visible () ? SCROLLBAR_THICKNESS : 0;\n\n   x = misc::min (x, canvasWidth - w() + hdiff);\n   x = misc::max (x, 0);\n\n   y = misc::min (y, canvasHeight - h() + vdiff);\n   y = misc::max (y, 0);\n\n   if (x == scrollX && y == scrollY) {\n      return;\n   }\n\n   /* multiple calls to scroll can happen before a redraw occurs.\n    * scrollDX and scrollDY can therefore be non-zero here.\n    */\n   updateCanvasWidgets (x - scrollX, y - scrollY);\n   scrollDX += x - scrollX;\n   scrollDY += y - scrollY;\n\n   scrollX = x;\n   scrollY = y;\n\n   adjustScrollbarValues ();\n   damage(FL_DAMAGE_SCROLL);\n   theLayout->scrollPosChanged (this, scrollX, scrollY);\n   positionChanged();\n}\n\nvoid FltkViewport::scroll (int dx, int dy)\n{\n   scrollTo (scrollX + dx, scrollY + dy);\n}\n\nvoid FltkViewport::scroll (core::ScrollCommand cmd)\n{\n   if (cmd == core::SCREEN_UP_CMD) {\n      scroll (0, -h () + vscrollbar->linesize ());\n   } else if (cmd == core::SCREEN_DOWN_CMD) {\n      scroll (0, h () - vscrollbar->linesize ());\n   } else if (cmd == core::SCREEN_LEFT_CMD) {\n      scroll (-w() + hscrollbar->linesize (), 0);\n   } else if (cmd == core::SCREEN_RIGHT_CMD) {\n      scroll (w() - hscrollbar->linesize (), 0);\n   } else if (cmd == core::LINE_UP_CMD) {\n      scroll (0, (int) -vscrollbar->linesize ());\n   } else if (cmd == core::LINE_DOWN_CMD) {\n      scroll (0, (int) vscrollbar->linesize ());\n   } else if (cmd == core::LEFT_CMD) {\n      scroll ((int) -hscrollbar->linesize (), 0);\n   } else if (cmd == core::RIGHT_CMD) {\n      scroll ((int) hscrollbar->linesize (), 0);\n   } else if (cmd == core::TOP_CMD) {\n      scrollTo (scrollX, 0);\n   } else if (cmd == core::BOTTOM_CMD) {\n      scrollTo (scrollX, canvasHeight); /* gets adjusted in scrollTo () */\n   }\n}\n\n/*\n * Scrolling in response to selection where the cursor is outside the view.\n */\nvoid FltkViewport::selectionScroll ()\n{\n   int distance;\n   int dx = 0, dy = 0;\n\n   if ((distance = x() - mouse_x) > 0)\n      dx = -distance * hscrollbar->linesize () / 48 - 1;\n   else if ((distance = mouse_x - (x() + w())) > 0)\n      dx = distance * hscrollbar->linesize () / 48 + 1;\n   if ((distance = y() - mouse_y) > 0)\n      dy = -distance * vscrollbar->linesize () / 48 - 1;\n   else if ((distance = mouse_y - (y() + h())) > 0)\n      dy = distance * vscrollbar->linesize () / 48 + 1;\n\n   scroll (dx, dy);\n}\n\nvoid FltkViewport::selectionScroll (void *data)\n{\n   ((FltkViewport *)data)->selectionScroll ();\n   Fl::repeat_timeout(0.025, selectionScroll, data);\n}\n\nvoid FltkViewport::setViewportSize (int width, int height,\n                                    int hScrollbarThickness,\n                                    int vScrollbarThickness)\n{\n   int adjustReq =\n      (hscrollbar->visible() ? !hScrollbarThickness : hScrollbarThickness) ||\n      (vscrollbar->visible() ? !vScrollbarThickness : vScrollbarThickness);\n\n   _MSG(\"FltkViewport::setViewportSize old_w,old_h=%dx%d -> w,h=%dx%d\\n\"\n       \"\\t hThick=%d hVis=%d, vThick=%d vVis=%d, adjustReq=%d\\n\",\n       w(),h(),width,height,\n       hScrollbarThickness,hscrollbar->visible(),\n       vScrollbarThickness,vscrollbar->visible(), adjustReq);\n\n   (hScrollbarThickness > 0) ? hscrollbar->show () : hscrollbar->hide ();\n   (vScrollbarThickness > 0) ? vscrollbar->show () : vscrollbar->hide ();\n\n   /* If no scrollbar, go to the beginning */\n   scroll(hScrollbarThickness ? 0 : -scrollX,\n          vScrollbarThickness ? 0 : -scrollY);\n\n   /* Adjust when scrollbar visibility changes */\n   if (adjustReq)\n      adjustScrollbarsAndGadgetsAllocation ();\n}\n\nvoid FltkViewport::updateCanvasWidgets (int dx, int dy)\n{\n   // scroll all child widgets except scroll bars\n   for (int i = children () - 1; i > 0; i--) {\n      Fl_Widget *widget = child (i);\n\n      if (widget == hscrollbar || widget == vscrollbar)\n         continue;\n\n      widget->position(widget->x () - dx, widget->y () - dy);\n   }\n}\n\nint FltkViewport::translateViewXToCanvasX (int X)\n{\n   return X - x () + scrollX;\n}\n\nint FltkViewport::translateViewYToCanvasY (int Y)\n{\n   return Y - y () + scrollY;\n}\n\nint FltkViewport::translateCanvasXToViewX (int X)\n{\n   return X + x () - scrollX;\n}\n\nint FltkViewport::translateCanvasYToViewY (int Y)\n{\n   return Y + y () - scrollY;\n}\n\n// ----------------------------------------------------------------------\n\nvoid FltkViewport::setGadgetOrientation (bool hscrollbarVisible,\n                                         bool vscrollbarVisible,\n                                         FltkViewport::GadgetOrientation\n                                         gadgetOrientation)\n{\n   this->gadgetOrientation[(hscrollbarVisible ? 0 : 1) |\n                           (vscrollbarVisible ? 0 : 2)] = gadgetOrientation;\n   adjustScrollbarsAndGadgetsAllocation ();\n}\n\nvoid FltkViewport::addGadget (Fl_Widget *gadget)\n{\n   /** \\bug Reparent? */\n\n   gadgets->append (new TypedPointer < Fl_Widget> (gadget));\n   adjustScrollbarsAndGadgetsAllocation ();\n}\n\n\n} // namespace fltk\n} // namespace dw\n"
  },
  {
    "path": "dw/fltkviewport.hh",
    "content": "#ifndef __DW_FLTKVIEWPORT_HH__\n#define __DW_FLTKVIEWPORT_HH__\n\n#include <FL/Fl_Group.H>\n#include <FL/Fl_Scrollbar.H>\n\n#include \"core.hh\"\n#include \"fltkcore.hh\"\n#include \"fltkviewbase.hh\"\n\nnamespace dw {\nnamespace fltk {\n\nclass FltkViewport: public FltkWidgetView\n{\npublic:\n   enum GadgetOrientation { GADGET_VERTICAL, GADGET_HORIZONTAL };\n\nprivate:\n   enum { SCROLLBAR_THICKNESS = 15 };\n\n   int scrollX, scrollY;\n   int scrollDX, scrollDY;\n   int hasDragScroll, dragScrolling, dragX, dragY;\n   int horScrolling, verScrolling;\n\n   Fl_Scrollbar *vscrollbar, *hscrollbar;\n\n   GadgetOrientation gadgetOrientation[4];\n   lout::container::typed::List <lout::object::TypedPointer < Fl_Widget> >\n      *gadgets;\n\n   void adjustScrollbarsAndGadgetsAllocation ();\n   void adjustScrollbarValues ();\n   void hscrollbarChanged ();\n   void vscrollbarChanged ();\n   void positionChanged ();\n\n   static void hscrollbarCallback (Fl_Widget *hscrollbar, void *viewportPtr);\n   static void vscrollbarCallback (Fl_Widget *vscrollbar, void *viewportPtr);\n\n   void selectionScroll();\n   static void selectionScroll(void *vport);\n\n   void updateCanvasWidgets (int oldScrollX, int oldScrollY);\n   static void draw_area (void *data, int x, int y, int w, int h);\n\nprotected:\n   int translateViewXToCanvasX (int x);\n   int translateViewYToCanvasY (int y);\n   int translateCanvasXToViewX (int x);\n   int translateCanvasYToViewY (int y);\n\npublic:\n   FltkViewport (int x, int y, int w, int h, const char *label = 0);\n   ~FltkViewport ();\n\n   void resize(int x, int y, int w, int h);\n   void draw ();\n   int handle (int event);\n\n   void setCanvasSize (int width, int ascent, int descent);\n\n   bool usesViewport ();\n   int getHScrollbarThickness ();\n   int getVScrollbarThickness ();\n   void scroll(int dx, int dy);\n   void scroll(dw::core::ScrollCommand cmd);\n   void scrollTo (int x, int y);\n   void setViewportSize (int width, int height,\n                         int hScrollbarThickness, int vScrollbarThickness);\n   void setScrollStep(int step);\n\n   void setGadgetOrientation (bool hscrollbarVisible, bool vscrollbarVisible,\n                              GadgetOrientation gadgetOrientation);\n   void setDragScroll (bool enable) { hasDragScroll = enable ? 1 : 0; }\n   void addGadget (Fl_Widget *gadget);\n};\n\n} // namespace fltk\n} // namespace dw\n\n#endif // __DW_FLTKVIEWPORT_HH__\n\n"
  },
  {
    "path": "dw/hyphenator.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2012-2013 Sebastian Geerken <sgeerken@dillo.org>,\n *                     Johannes Hofmann <Johannes.Hofmann@gmx.de>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"hyphenator.hh\"\n\n#include \"../lout/misc.hh\"\n#include \"../lout/unicode.hh\"\n#include <limits.h>\n#include <stdio.h>\n#include <string.h>\n\n#define LEN 1000\n\n/*\n * This is (or at least began as) a direct translation of Ned Batchelder's\n * public-domain Python implementation at\n * http://nedbatchelder.com/code/modules/hyphenate.py\n */\n\nusing namespace lout::object;\nusing namespace lout::container::typed;\nusing namespace lout::misc;\nusing namespace lout::unicode;\n\nnamespace dw {\n\nHashTable <String, Hyphenator> *Hyphenator::hyphenators =\n   new HashTable <String, Hyphenator> (true, true);\n\nHyphenator::Hyphenator (const char *patFile, const char *excFile, int pack)\n{\n   trie = NULL; // As long we are not sure whether a pattern file can be read.\n\n   int bufLen = strlen (patFile) + 5 + 1;\n   char *buf = new char[bufLen];\n   snprintf(buf, bufLen, \"%s.trie\", patFile);\n   FILE *trieF = fopen (buf, \"r\");\n   delete[] buf;\n\n   if (trieF) {\n      trie = new Trie ();\n      if (trie->load (trieF) != 0) {\n         delete trie;\n         trie = NULL;\n      }\n      fclose (trieF);\n   }\n\n   if (trie == NULL) {\n      TrieBuilder trieBuilder(pack);\n      FILE *patF = fopen (patFile, \"r\");\n      if (patF) {\n\n         while (!feof (patF)) {\n            char buf[LEN + 1];\n            char *s = fgets (buf, LEN, patF);\n            if (s && s[0] != '%') { // ignore lines starting with '%' as comment\n               // TODO Better exit with an error, when the line is too long.\n               int l = strlen (s);\n               if (s[l - 1] == '\\n')\n                  s[l - 1] = 0;\n               insertPattern (&trieBuilder, s);\n            }\n         }\n\n         trie = trieBuilder.createTrie ();\n         fclose (patF);\n      }\n   }\n\n   exceptions = NULL; // Again, only instantiated when needed.\n\n   FILE *excF = fopen (excFile, \"r\");\n   if (excF) {\n      exceptions = new HashTable <ConstString, Vector <Integer> > (true, true);\n      while (!feof (excF)) {\n         char buf[LEN + 1];\n         char *s = fgets (buf, LEN, excF);\n         if (s && s[0] != '%') { // ignore lines starting with '%' as comment\n             // TODO Better exit with an error, when the line is too long.\n            int l = strlen (s);\n            if (s[l - 1] == '\\n')\n               s[l - 1] = 0;\n            insertException (s);\n         }\n      }\n      fclose (excF);\n   }\n}\n\nHyphenator::~Hyphenator ()\n{\n   delete trie;\n   delete exceptions;\n}\n\nHyphenator *Hyphenator::getHyphenator (const char *lang)\n{\n   String *langString = new String (lang);\n\n   Hyphenator *hyphenator = hyphenators->get (langString);\n   if (hyphenator)\n      delete langString;\n   else {\n      int patFileLen = strlen (DILLO_LIBDIR) + 13 + strlen (lang) + 4 + 1;\n      char *patFile = new char[patFileLen];\n      snprintf (patFile, patFileLen, \"%s/hyphenation/%s.pat\",\n                DILLO_LIBDIR, lang);\n      int excFileLen = strlen (DILLO_LIBDIR) + 13 + strlen (lang) + 4 + 1;\n      char *excFile = new char[excFileLen];\n      snprintf (excFile, excFileLen, \"%s/hyphenation/%s.exc\",\n                DILLO_LIBDIR, lang);\n\n      //printf (\"Loading hyphenation patterns for language '%s' from '%s' and \"\n      //        \"exceptions from '%s' ...\\n\", lang, patFile, excFile);\n\n      hyphenator = new Hyphenator (patFile, excFile);\n      hyphenators->put (langString, hyphenator);\n      delete[] patFile;\n      delete[] excFile;\n   }\n\n   //lout::misc::StringBuffer sb;\n   //hyphenators->intoStringBuffer (&sb);\n   //printf (\"%s\\n\", sb.getChars ());\n\n   return hyphenator;\n}\n\nvoid Hyphenator::insertPattern (TrieBuilder *trieBuilder, char *s)\n{\n   // Convert the a pattern like 'a1bc3d4' into a string of chars 'abcd'\n   // and a list of points [ 0, 1, 0, 3, 4 ].\n   int l = strlen (s);\n   char *chars = new char[l + 1];\n   SimpleVector<char> points (1);\n\n   // TODO numbers consisting of multiple digits?\n   // TODO Encoding: This implementation works exactly like the Python\n   // implementation, based on UTF-8. Does this always work?\n   int numChars = 0;\n   for (int i = 0; s[i]; i++) {\n      if (s[i] >= '0' && s[i] <= '9') {\n         points.setSize(numChars + 1, '0');\n         points.set(numChars, s[i]);\n      } else {\n         chars[numChars++] = s[i];\n      }\n   }\n   chars[numChars] = 0;\n\n   points.setSize(numChars + 2, '0');\n   points.set(numChars + 1, '\\0');\n\n   // Insert the pattern into the tree.  Each character finds a dict\n   // another level down in the tree, and leaf nodes have the list of\n   // points.\n\n   //printf(\"insertPattern %s\\n\", chars);\n\n   trieBuilder->insert (chars, points.getArray ());\n   delete[] chars;\n}\n\nvoid Hyphenator::insertException (char *s)\n{\n   Vector<Integer> *breaks = new Vector<Integer> (1, true);\n\n   int len = strlen (s);\n   for (int i = 0; i < len - 1; i++)\n      if((unsigned char)s[i] == 0xc2 && (unsigned char)s[i + 1] == 0xad)\n         breaks->put (new Integer (i - 2 * breaks->size()));\n\n   char *noHyphens = new char[len - 2 * breaks->size() + 1];\n   int j = 0;\n   for (int i = 0; i < len; ) {\n      if(i < len - 1 &&\n         (unsigned char)s[i] == 0xc2 && (unsigned char)s[i + 1] == 0xad)\n         i += 2;\n      else\n         noHyphens[j++] = s[i++];\n   }\n   noHyphens[j] = 0;\n\n   exceptions->put (new String (noHyphens), breaks);\n   delete[] noHyphens;\n}\n\n/**\n * Simple test to avoid much costs. Passing it does not mean that the word\n * can be hyphenated.\n */\nbool Hyphenator::isHyphenationCandidate (const char *word)\n{\n   // Short words aren't hyphenated.\n   return (strlen (word) > 4); // TODO UTF-8?\n}\n\n/**\n * Test whether the character on which \"s\" points (UTF-8) is an actual\n * part of the word. Other characters at the beginning and end are\n * ignored.\n *\n * TODO Currently only suitable for English and German.\n * TODO Only lowercase. (Uppercase not needed.)\n */\nbool Hyphenator::isCharPartOfActualWord (char *s)\n{\n   return isAlpha (decodeUtf8 (s));\n}\n\n/**\n * Given a word, returns a list of the possible hyphenation points.\n */\nint *Hyphenator::hyphenateWord(core::Platform *platform,\n                               const char *word, int *numBreaks)\n{\n   if ((trie == NULL && exceptions == NULL) || !isHyphenationCandidate (word)) {\n      *numBreaks = 0;\n      return NULL;\n   }\n\n   char *wordLc = platform->textToLower (word, strlen (word));\n\n   int start = 0;\n   SimpleVector <int> breakPos (1);\n\n   // Split the original word up, ignore anything but characters, and\n   // collect all break points, so that they fit to the original\n   // word. (The latter is what the offset in the call of\n   // hyphenateSingleWord() is for.)\n   while (true) {\n      while (wordLc[start] && !isCharPartOfActualWord (wordLc + start))\n         start = platform->nextGlyph (wordLc, start);\n\n      if (wordLc[start] == 0)\n         break;\n\n      int end = start, i = end;\n      while (wordLc[i]) {\n         if (!isCharPartOfActualWord (wordLc + i))\n            break;\n         else\n            end = i;\n         i = platform->nextGlyph (wordLc, i);\n      }\n      end = platform->nextGlyph (wordLc, end);\n\n      int nextStart;\n      if (wordLc[end]) {\n         nextStart = platform->nextGlyph (wordLc, end);\n         wordLc[end] = 0;\n      } else\n         nextStart = end;\n\n      hyphenateSingleWord (platform, wordLc + start, start, &breakPos);\n      start = nextStart;\n   }\n\n   free (wordLc);\n\n   *numBreaks = breakPos.size ();\n   if (*numBreaks == 0)\n      return NULL;\n   else {\n      return breakPos.detachArray ();\n   }\n}\n\n/**\n * Hyphenate a single word, which only consists of lowercase\n * characters. Store break positions + \"offset\" in \"breakPos\".\n */\nvoid Hyphenator::hyphenateSingleWord(core::Platform *platform,\n                                     char *wordLc, int offset,\n                                     SimpleVector <int> *breakPos)\n{\n   // If the word is an exception, get the stored points.\n   Vector <Integer> *exceptionalBreaks;\n   ConstString key (wordLc);\n   if (exceptions != NULL && (exceptionalBreaks = exceptions->get (&key))) {\n      for (int i = 0; i < exceptionalBreaks->size(); i++) {\n         breakPos->increase ();\n         breakPos->set (breakPos->size() - 1,\n                        exceptionalBreaks->get(i)->getValue() + offset);\n      }\n      return;\n   }\n\n\n   // trie == NULL means that there is no pattern file.\n   if (trie == NULL)\n      return;\n\n   char *work = new char[strlen (wordLc) + 3];\n   strcpy (work, \".\");\n   strcat (work, wordLc);\n   strcat (work, \".\");\n\n   int l = strlen (work);\n   SimpleVector <int> points (l + 1);\n   points.setSize (l + 1, 0);\n\n   for (int i = 0; i < l; i++) {\n      int state = trie->root;\n\n      for (int j = i; j < l && trie->validState (state); j++) {\n         const char *p = trie->getData((unsigned char) work[j], &state);\n\n         if (p) {\n            for (int k = 0; p[k]; k++)\n               points.set(i + k,\n                          lout::misc::max (points.get (i + k), p[k] - '0'));\n         }\n      }\n   }\n\n   delete[] work;\n\n   // No hyphens in the first two chars or the last two.\n   // Characters are not bytes, so UTF-8 characters must be counted.\n   const char *s = nextUtf8Char (wordLc);\n   if (s != NULL && (s = nextUtf8Char (s)) != NULL) {\n      // First two characters.\n      int bytesStart = s - wordLc;\n      for (int i = 0; i < bytesStart; i++)\n         points.set (i + 1, 0);\n\n      // Last two characters: instead of iterating back from the end,\n      // we simply iterate from the start to the end and count the\n      // characters.\n\n      int lenBytes = strlen (wordLc);\n      int lenUtf8 = numUtf8Chars (wordLc);\n      int bytesEnd = 0;\n\n      s = wordLc;\n      for (int i = 0; s; s = nextUtf8Char (s), i++) {\n         if (i == lenUtf8 - 2)\n            bytesEnd = lenBytes - (s - wordLc);\n      }\n\n      for (int i = 0; i < bytesEnd; i++)\n         points.set (points.size() - 2 - i, 0);\n   }\n\n   // Examine the points to build the break point list.\n   int n = lout::misc::min ((int)strlen (wordLc), points.size () - 2);\n   for (int i = 0; i < n; i++) {\n      if (points.get(i + 2) % 2) {\n         breakPos->increase ();\n         breakPos->set (breakPos->size() - 1, i + 1 + offset);\n      }\n   }\n}\n\nTrie::TrieNode TrieBuilder::trieNodeNull = {'\\0', 0, NULL};\n\nTrieBuilder::TrieBuilder (int pack)\n{\n   this->pack = pack;\n   dataList = new SimpleVector <DataEntry> (10000);\n   stateStack = new SimpleVector <StackEntry> (10);\n   tree = new SimpleVector <Trie::TrieNode> (20000);\n   dataZone = new ZoneAllocator (1024);\n   stateStackPush(0);\n}\n\nTrieBuilder::~TrieBuilder ()\n{\n   delete dataList;\n   delete stateStack;\n   delete tree;\n   delete dataZone;\n}\n\nvoid TrieBuilder::insert (const char *key, const char *value)\n{\n   dataList->increase ();\n   dataList->getLastRef ()->key = (unsigned char *) strdup(key);\n   dataList->getLastRef ()->value = dataZone->strdup (value);\n}\n\nint TrieBuilder::keyCompare (const void *p1, const void *p2)\n{\n   DataEntry *pd1 = (DataEntry *) p1;\n   DataEntry *pd2 = (DataEntry *) p2;\n\n   return strcmp ((char *) pd1->key, (char *) pd2->key);\n}\n\nint TrieBuilder::insertState (StackEntry *state, bool root)\n{\n   int i, j;\n\n   if (state->count == 0)\n      return 0;\n\n   if (root) {\n      i = 0; // we reseve slot 0 for the root state\n   } else {\n      /* The bigger pack is the more slots we check and the smaller\n       * the trie will be, but CPU consumption also increases.\n       * Reasonable values for pack seemt to be between 256 and 1024.\n       */\n      i = tree->size () - pack + 2 * state->count;\n\n      if (i < 256) // reserve first 256 entries for the root state\n         i = 256;\n   }\n\n   for (;; i++) {\n      if (i + 256 > tree->size ())\n         tree->setSize (i + 256, trieNodeNull);\n\n      for (j = 1; j < 256; j++) {\n         Trie::TrieNode *tn = tree->getRef(i + j);\n\n         if (tn->c == j || ((state->next[j] || state->data[j]) && tn->c != 0))\n            break;\n      }\n\n      if (j == 256) // found a suitable slot\n         break;\n   }\n\n   for (int j = 1; j < 256; j++) {\n      Trie::TrieNode *tn = tree->getRef(i + j);\n\n      if (state->next[j] || state->data[j]) {\n         tn->c = j;\n         tn->next = state->next[j];\n         tn->data = state->data[j];\n      }\n   }\n\n   assert (root || i >= 256);\n   assert (!root || i == 0);\n   return i;\n}\n\nvoid TrieBuilder::stateStackPush (unsigned char c)\n{\n   stateStack->increase ();\n   StackEntry *e = stateStack->getLastRef ();\n   memset (e, 0, sizeof (StackEntry));\n   e->c = c;\n}\n\nint TrieBuilder::stateStackPop ()\n{\n   int next = insertState (stateStack->getLastRef (), stateStack->size () == 1);\n   unsigned char c = stateStack->getLastRef ()->c;\n   const char *data = stateStack->getLastRef ()->data1;\n\n   stateStack->setSize (stateStack->size () - 1);\n\n   if (stateStack->size () > 0) {\n      assert (stateStack->getLastRef ()->next[c] == 0);\n      assert (stateStack->getLastRef ()->data[c] == NULL);\n      stateStack->getLastRef ()->next[c] = next;\n      stateStack->getLastRef ()->data[c] = data;\n      stateStack->getLastRef ()->count++;\n   }\n\n   return next;\n}\n\nTrie *TrieBuilder::createTrie ()\n{\n   // we need to sort the patterns as byte strings not as unicode\n   qsort (dataList->getArray (), dataList->size (),\n      sizeof (DataEntry), keyCompare);\n\n   for (int i = 0; i < dataList->size (); i++) {\n      insertSorted (dataList->getRef (i)->key, dataList->getRef (i)->value);\n      free (dataList->getRef (i)->key);\n   }\n\n   while (stateStack->size ())\n      stateStackPop ();\n\n   int size = tree->size ();\n   Trie *trie = new Trie(tree->detachArray(), size, true, dataZone);\n   dataZone = NULL;\n   return trie;\n}\n\nvoid TrieBuilder::insertSorted (unsigned char *s, const char *data)\n{\n   int len = strlen((char*)s);\n\n   for (int i = 0; i < len; i++) {\n      if (stateStack->size () > i + 1 &&\n          stateStack->getRef (i + 1)->c != s[i]) {\n         for (int j = stateStack->size () - 1; j >= i + 1; j--)\n            stateStackPop();\n      }\n\n      if (i + 1 >= stateStack->size ())\n         stateStackPush(s[i]);\n   }\n\n   while (stateStack->size () > len + 1)\n      stateStackPop();\n\n   assert (stateStack->size () == len + 1);\n   stateStack->getLastRef ()->data1 = data;\n}\n\nTrie::Trie (TrieNode *array, int size, bool freeArray, ZoneAllocator *dataZone)\n{\n   this->array = array;\n   this->size = size;\n   this->freeArray = freeArray;\n   this->dataZone = dataZone;\n}\n\nTrie::~Trie ()\n{\n   delete dataZone;\n   if (freeArray)\n      free(array);\n}\n\nvoid Trie::save (FILE *file)\n{\n   for (int i = 0; i < size; i++) {\n      Trie::TrieNode *tn = &array[i];\n\n      if (tn->data)\n         fprintf(file, \"%u, %u, %s\\n\", tn->c, tn->next, tn->data);\n      else\n         fprintf(file, \"%u, %u\\n\", tn->c, tn->next);\n   }\n}\n\nint Trie::load (FILE *file)\n{\n   int next, c, maxNext = 0;\n   SimpleVector <TrieNode> tree (100);\n   dataZone = new ZoneAllocator (1024);\n\n   while (!feof (file)) {\n      char buf[LEN + 1];\n      char *s = fgets (buf, LEN, file);\n\n      if (!s)\n         continue;\n\n      char data[LEN + 1];\n      int n = sscanf (s, \"%d, %d, %s\", &c, &next, data);\n\n      if (n >= 2 && c >= 0 && c < 256 && next >= 0) {\n         tree.increase ();\n         tree.getLastRef ()->c = c;\n         tree.getLastRef ()->next = next;\n         if (n >= 3)\n            tree.getLastRef ()->data = dataZone->strdup (data);\n         else\n            tree.getLastRef ()->data = NULL;\n\n         if (next > maxNext)\n            maxNext = next;\n      } else {\n         goto error;\n      }\n   }\n\n   if (maxNext >= tree.size ())\n      goto error;\n\n   size = tree.size ();\n   array = tree.detachArray ();\n   freeArray = true;\n   return 0;\n\nerror:\n   delete dataZone;\n   dataZone = NULL;\n   return 1;\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/hyphenator.hh",
    "content": "#ifndef __DW_HYPHENATOR_HH__\n#define __DW_HYPHENATOR_HH__\n\n#include \"../lout/object.hh\"\n#include \"../lout/container.hh\"\n#include \"../dw/core.hh\"\n\nnamespace dw {\n\nclass Trie {\n   public:\n      struct TrieNode {\n         unsigned char c;\n         uint16_t next;\n         const char *data;\n      };\n\n   private:\n      TrieNode *array;\n      int size;\n      bool freeArray;\n      lout::misc::ZoneAllocator *dataZone;\n\n   public:\n      Trie (TrieNode *array = NULL, int size = 0, bool freeArray = false,\n            lout::misc::ZoneAllocator *dataZone = NULL);\n      ~Trie ();\n\n      static const int root = 0;\n      inline bool validState (int state) { return state >= 0 && state < size; };\n      inline const char *getData (unsigned char c, int *state)\n      {\n         if (!validState (*state))\n            return NULL;\n\n         TrieNode *tn = array + *state + c;\n\n         if (tn->c == c) {\n            *state = tn->next > 0 ? tn->next : -1;\n            return tn->data;\n         } else {\n            *state = -1;\n            return NULL;\n         }\n      };\n      void save (FILE *file);\n      int load (FILE *file);\n};\n\nclass TrieBuilder {\n   private:\n      struct StackEntry {\n         unsigned char c;\n         int count;\n         int next[256];\n         const char *data[256];\n         const char *data1;\n      };\n\n      struct DataEntry {\n         unsigned char *key;\n         const char *value;\n      };\n\n      int pack;\n      static Trie::TrieNode trieNodeNull;\n      lout::misc::SimpleVector <Trie::TrieNode> *tree;\n      lout::misc::SimpleVector <DataEntry> *dataList;\n      lout::misc::SimpleVector <StackEntry> *stateStack;\n      lout::misc::ZoneAllocator *dataZone;\n\n      static int keyCompare (const void *p1, const void *p2);\n      void stateStackPush (unsigned char c);\n      int stateStackPop ();\n      int insertState (StackEntry *state, bool root);\n      void insertSorted (unsigned char *key, const char *value);\n\n   public:\n      TrieBuilder (int pack);\n      ~TrieBuilder ();\n\n      void insert (const char *key, const char *value);\n      Trie *createTrie();\n};\n\nclass Hyphenator: public lout::object::Object\n{\n   static lout::container::typed::HashTable\n      <lout::object::String, Hyphenator> *hyphenators;\n   Trie *trie;\n\n   lout::container::typed::HashTable <lout::object::ConstString,\n                                      lout::container::typed::Vector\n                                      <lout::object::Integer> > *exceptions;\n\n   void insertPattern (TrieBuilder *trieBuilder, char *s);\n   void insertException (char *s);\n\n   void hyphenateSingleWord(core::Platform *platform, char *wordLc, int offset,\n                            lout::misc::SimpleVector <int> *breakPos);\n   bool isCharPartOfActualWord (char *s);\n\npublic:\n   Hyphenator (const char *patFile, const char *excFile, int pack = 256);\n   ~Hyphenator();\n\n   static Hyphenator *getHyphenator (const char *language);\n   static bool isHyphenationCandidate (const char *word);\n   int *hyphenateWord(core::Platform *platform, const char *word, int *numBreaks);\n   void saveTrie (FILE *fp) { trie->save (fp); };\n};\n\n} // namespace dw\n\n#endif // __DW_HYPHENATOR_HH__\n"
  },
  {
    "path": "dw/image.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"image.hh\"\n#include \"../lout/msg.h\"\n#include \"../lout/misc.hh\"\n#include \"../lout/debug.hh\"\n\nnamespace dw {\n\nusing namespace lout;\n\nImageMapsList::ImageMap::ImageMap ()\n{\n   shapesAndLinks = new container::typed::List <ShapeAndLink> (true);\n   defaultLink = -1;\n}\n\nImageMapsList::ImageMap::~ImageMap ()\n{\n   delete shapesAndLinks;\n}\n\nvoid ImageMapsList::ImageMap::draw (core::View *view,core::style::Style *style,\n                                    int x, int y)\n{\n   container::typed::Iterator <ShapeAndLink> it;\n\n   for (it = shapesAndLinks->iterator (); it.hasNext (); ) {\n      ShapeAndLink *shapeAndLink = it.getNext ();\n\n      shapeAndLink->shape->draw(view, style, x, y);\n   }\n}\n\nvoid ImageMapsList::ImageMap::add (core::Shape *shape, int link) {\n   ShapeAndLink *shapeAndLink = new ShapeAndLink ();\n   shapeAndLink->shape = shape;\n   shapeAndLink->link = link;\n   shapesAndLinks->append (shapeAndLink);\n}\n\nint ImageMapsList::ImageMap::link (int x, int y) {\n   container::typed::Iterator <ShapeAndLink> it;\n   int link = defaultLink;\n\n   for (it = shapesAndLinks->iterator (); it.hasNext (); ) {\n      ShapeAndLink *shapeAndLink = it.getNext ();\n\n      if (shapeAndLink->shape->isPointWithin (x, y)) {\n         link = shapeAndLink->link;\n         break;\n      }\n   }\n\n   return link;\n}\n\nImageMapsList::ImageMapsList ()\n{\n   imageMaps = new container::typed::HashTable <object::Object, ImageMap>\n      (true, true);\n   currentMap = NULL;\n}\n\nImageMapsList::~ImageMapsList ()\n{\n   delete imageMaps;\n}\n\n/**\n * \\brief Start a new map and make it the current one.\n *\n * This has to be called before dw::ImageMapsList::addShapeToCurrentMap.\n * \"key\" is owned by the image map list, so a copy should be passed, when\n * necessary.\n */\nvoid ImageMapsList::startNewMap (object::Object *key)\n{\n   currentMap = new ImageMap ();\n   imageMaps->put (key, currentMap);\n}\n\n/**\n * \\brief Add a shape to the current map-\n *\n * \"shape\" is owned by the image map list, so a copy should be passed, when\n * necessary.\n */\nvoid ImageMapsList::addShapeToCurrentMap (core::Shape *shape, int link)\n{\n   currentMap->add (shape, link);\n}\n\n/**\n * \\brief Set default link for current map-\n */\nvoid ImageMapsList::setCurrentMapDefaultLink (int link)\n{\n   currentMap->setDefaultLink (link);\n}\n\nvoid ImageMapsList::drawMap (lout::object::Object *key, core::View *view,\n                             core::style::Style *style, int x, int y)\n{\n   ImageMap *map = imageMaps->get (key);\n\n   if (map)\n      map->draw(view, style, x, y);\n}\n\nint ImageMapsList::link (object::Object *key, int x, int y)\n{\n   int link = -1;\n   ImageMap *map = imageMaps->get (key);\n\n   if (map)\n      link = map->link (x, y);\n\n   return link;\n}\n\n// ----------------------------------------------------------------------\n\nint Image::CLASS_ID = -1;\n\nint Image::lastallocationx = 0;\n\nImage::Image(const char *altText)\n{\n   DBG_OBJ_CREATE (\"dw::Image\");\n   registerName (\"dw::Image\", &CLASS_ID);\n   this->altText = altText ? strdup (altText) : NULL;\n   altTextWidth = -1; // not yet calculated\n   buffer = NULL;\n   bufWidth = bufHeight = -1;\n   clicking = false;\n   currLink = -1;\n   mapList = NULL;\n   mapKey = NULL;\n   isMap = false;\n\n   DBG_OBJ_SET_NUM (\"bufWidth\", bufWidth);\n   DBG_OBJ_SET_NUM (\"bufHeight\", bufHeight);\n}\n\nImage::~Image()\n{\n   if (altText)\n      free(altText);\n   if (buffer)\n      buffer->unref ();\n   if (mapKey)\n      delete mapKey;\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid Image::sizeRequestSimpl (core::Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequestImpl\");\n\n   int styleWidth = core::style::absLengthVal (getStyle ()->width);\n   int styleMaxWidth = core::style::absLengthVal (getStyle ()->maxWidth);\n   int styleHeight = core::style::absLengthVal (getStyle ()->height);\n  \n   if (buffer) {\n      requisition->width = buffer->getRootWidth ();\n\n      // respect CSS width and max-width\n      if (styleWidth >0)\n         requisition->width = styleMaxWidth > 0 && styleMaxWidth < styleWidth ?\n                              styleMaxWidth : styleWidth;\n      else if (styleMaxWidth > 0 && styleMaxWidth < requisition->width)\n         requisition->width = styleMaxWidth;\n      \n      requisition->ascent = buffer->getRootHeight ();\n      requisition->descent = 0;\n   } else {\n      if (altText && altText[0]) {\n         if (altTextWidth == -1)\n            altTextWidth =\n               layout->textWidth (getStyle()->font, altText, strlen (altText));\n\n         requisition->width = altTextWidth;\n         requisition->ascent = getStyle()->font->ascent;\n         requisition->descent = getStyle()->font->descent;\n      } else {\n         requisition->width = 0;\n         requisition->ascent = 0;\n         requisition->descent = 0;\n      }\n   }\n\n   requisition->width += boxDiffWidth ();\n   requisition->ascent += boxOffsetY ();\n   requisition->descent += boxRestHeight ();\n\n   correctRequisition (requisition, core::splitHeightPreserveDescent, true,\n                       true);\n\n   if (buffer) {\n      // If one dimension is set, preserve the aspect ratio (without\n      // extraSpace/margin/border/padding). Notice that\n      // requisition->descent could have been changed in\n      // core::splitHeightPreserveDescent, so we do not make any\n      // assumtions here about it (and requisition->ascent).\n\n      // TODO Check again possible overflows. (Aren't buffer\n      // dimensions limited to 2^15?)\n\n      bool widthSpecified = getStyle()->width != core::style::LENGTH_AUTO; /*||\n         getStyle()->minWidth != core::style::LENGTH_AUTO ||\n         getStyle()->maxWidth != core::style::LENGTH_AUTO;*/\n      bool heightSpecified = getStyle()->height != core::style::LENGTH_AUTO;/* ||\n         getStyle()->minHeight != core::style::LENGTH_AUTO ||\n         getStyle()->maxHeight != core::style::LENGTH_AUTO;*/\n\n      // XXX: After a browser windows is resized and the page is\n      // refreshed allocation.x becomes -1 in this scope and I don't\n      // know of a better way to check what the new value should be.\n      // This public static int Image::lastallocationx is my hacked\n      // way of trying to remember it. It's updated in Image::sizeAllocateImpl\n      // Surely there's a better way :p. Nevertheless, this seems to\n      // work during the initial page load.\n      if (allocation.x < 0)\n         allocation.x = lastallocationx;\n      else\n         lastallocationx = allocation.x;\n\n      // XXX: If an image is not going to fit in the viewport, resize it to fit\n      // TODO: I ignored boxDiffWidth() and boxOffsetY() and boxRestHeight()\n      // for no good reason :s.\n      if ((allocation.x + requisition->width) > layout->getWidthViewport()) {\n         heightSpecified = false;\n         requisition->width = layout->getWidthViewport() - 2 * allocation.x;\n         requisition->ascent = requisition->width\n            * buffer->getRootHeight () / buffer->getRootWidth ();\n         requisition->descent = 0;\n      }\n      else if (!widthSpecified && heightSpecified) {\n         requisition->width =\n            (requisition->ascent + requisition->descent - boxDiffHeight ())\n            * buffer->getRootWidth () / buffer->getRootHeight ()\n            + boxDiffWidth ();\n      } else if (!heightSpecified) {\n         requisition->ascent = (requisition->width + boxDiffWidth ())\n            * buffer->getRootHeight () / buffer->getRootWidth ()\n            + boxOffsetY ();\n         requisition->descent = boxRestHeight ();\n      }\n      \n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"=> %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Image::getExtremesSimpl (core::Extremes *extremes)\n{\n   int contentWidth;\n   if (buffer)\n      contentWidth = buffer->getRootWidth ();\n   else {\n      if (altText && altText[0]) {\n         if (altTextWidth == -1)\n            altTextWidth =\n               layout->textWidth (getStyle()->font, altText, strlen (altText));\n         contentWidth = altTextWidth;\n      } else\n         contentWidth = 0;\n   }\n\n   // Adjust for CSS max-width\n   if(core::style::absLengthVal (getStyle ()->maxWidth) > 0 &&\n      core::style::absLengthVal (getStyle ()->maxWidth) < contentWidth)\n      contentWidth = core::style::absLengthVal (getStyle ()->maxWidth);\n\n   int width = contentWidth + boxDiffWidth ();\n\n   // With percentage width, the image may be narrower than the buffer.\n   extremes->minWidth =\n      core::style::isPerLength (getStyle()->width) ? boxDiffWidth () : width;\n\n   // (We ignore the same effect for the maximal width.)\n   extremes->maxWidth = width;\n\n   extremes->minWidthIntrinsic = extremes->minWidth;\n   extremes->maxWidthIntrinsic = extremes->maxWidth;\n\n   correctExtremes (extremes, false);\n\n   extremes->adjustmentWidth =\n      misc::min (extremes->minWidthIntrinsic, extremes->minWidth);\n}\n\nvoid Image::sizeAllocateImpl (core::Allocation *allocation)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"sizeAllocateImpl\", \"%d, %d; %d * (%d + %d)\",\n                  allocation->x, allocation->y, allocation->width,\n                  allocation->ascent, allocation->descent);\n\n\n   int newBufWidth = allocation->width - boxDiffWidth ();\n   int newBufHeight =\n      allocation->ascent + allocation->descent - boxDiffHeight ();\n\n   if (buffer && newBufWidth > 0 && newBufHeight > 0 &&\n       // Save some time when size did not change:\n       (newBufWidth != bufWidth || newBufHeight != bufHeight)) {\n      DBG_OBJ_MSG (\"resize\", 1, \"replacing buffer\");\n\n      core::Imgbuf *oldBuffer = buffer;\n      buffer = oldBuffer->getScaledBuf (newBufWidth, newBufHeight);\n      oldBuffer->unref ();\n\n      bufWidth = newBufWidth;\n      bufHeight = newBufHeight;\n\n      // TODO: Surely there's a better place (and a better way) to remember\n      // the allocation->x of an image?! This was the best place I could think\n      // of - at least it works with initial page loads - and _sortof_ works\n      // when pages are refreshed (although I had to force the value to 0\n      // in Image::setBuffer to avoid the more annoying issue of using stale\n      // values when isolating an image, or going back in cached history.\n      lastallocationx = allocation->x;\n\n      DBG_OBJ_ASSOC_CHILD (this->buffer);\n      DBG_OBJ_SET_NUM (\"bufWidth\", bufWidth);\n      DBG_OBJ_SET_NUM (\"bufHeight\", bufHeight);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Image::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n   // Nothing to do.\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Image::enterNotifyImpl (core::EventCrossing *event)\n{\n   // BUG: this is wrong for image maps, but the cursor position is unknown.\n   currLink = getStyle()->x_link;\n\n   if (currLink != -1) {\n      (void) layout->emitLinkEnter (this, currLink, -1, -1, -1);\n   }\n   Widget::enterNotifyImpl(event);\n}\n\nvoid Image::leaveNotifyImpl (core::EventCrossing *event)\n{\n   clicking = false;\n\n   if (currLink != -1) {\n      currLink = -1;\n      (void) layout->emitLinkEnter (this, -1, -1, -1, -1);\n   }\n   Widget::leaveNotifyImpl(event);\n}\n\n/*\n * Return the coordinate relative to the contents.\n * If the event occurred in the surrounding box, return the value at the\n * edge of the contents instead.\n */\nint Image::contentX (core::MousePositionEvent *event)\n{\n   int ret = event->xWidget - boxOffsetX();\n\n   ret = misc::min(getContentWidth(), misc::max(ret, 0));\n   return ret;\n}\n\nint Image::contentY (core::MousePositionEvent *event)\n{\n   int ret = event->yWidget - boxOffsetY();\n\n   ret = misc::min(getContentHeight(), misc::max(ret, 0));\n   return ret;\n}\n\nbool Image::motionNotifyImpl (core::EventMotion *event)\n{\n   if (mapList || isMap) {\n      int x = contentX(event);\n      int y = contentY(event);\n\n      if (mapList) {\n         /* client-side image map */\n         int newLink = mapList->link (mapKey, x, y);\n         if (newLink != currLink) {\n            currLink = newLink;\n            clicking = false;\n            /* \\todo Using MAP/AREA styles would probably be best */\n            setCursor(newLink == -1 ? getStyle()->cursor :\n                                      core::style::CURSOR_POINTER);\n            (void) layout->emitLinkEnter (this, newLink, -1, -1, -1);\n         }\n      } else if (isMap && currLink != -1) {\n         /* server-side image map */\n         (void) layout->emitLinkEnter (this, currLink, -1, x, y);\n      }\n   }\n   return true;\n}\n\nbool Image::buttonPressImpl (core::EventButton *event)\n{\n   bool ret = false;\n\n   currLink = mapList ? mapList->link(mapKey,contentX(event),contentY(event)) :\n      getStyle()->x_link;\n   if (event->button == 3){\n      (void)layout->emitLinkPress(this, currLink, getStyle()->x_img, -1, -1,\n                                  event);\n      ret = true;\n   } else if (event->button == 1 || currLink != -1){\n      clicking = true;\n      ret = true;\n   }\n   return ret;\n}\n\nbool Image::buttonReleaseImpl (core::EventButton *event)\n{\n   currLink = mapList ? mapList->link(mapKey,contentX(event),contentY(event)) :\n      getStyle()->x_link;\n   if (clicking) {\n      int x = isMap ? contentX(event) : -1;\n      int y = isMap ? contentY(event) : -1;\n      clicking = false;\n      layout->emitLinkClick (this, currLink, getStyle()->x_img, x, y, event);\n      return true;\n   }\n   return false;\n}\n\nvoid Image::draw (core::View *view, core::Rectangle *area,\n                  core::DrawingContext *context)\n{\n   int dx, dy;\n   core::Rectangle content, intersection;\n\n   drawWidgetBox (view, area, false);\n\n   if (buffer) {\n      dx = boxOffsetX ();\n      dy = boxOffsetY ();\n      content.x = dx;\n      content.y = dy;\n      content.width = getContentWidth ();\n      content.height = getContentHeight ();\n\n      if (area->intersectsWith (&content, &intersection))\n         view->drawImage (buffer,\n                          allocation.x + dx, allocation.y + dy,\n                          intersection.x - dx, intersection.y - dy,\n                          intersection.width, intersection.height);\n   } else {\n      core::View *clippingView;\n\n      if (altText && altText[0]) {\n         core::View *usedView = view;\n\n         clippingView = NULL;\n\n         if (altTextWidth == -1)\n            altTextWidth =\n               layout->textWidth (getStyle()->font, altText, strlen (altText));\n\n         if ((getContentWidth() < altTextWidth) ||\n             (getContentHeight() <\n              getStyle()->font->ascent + getStyle()->font->descent)) {\n            clippingView = usedView =\n               view->getClippingView (allocation.x + boxOffsetX (),\n                                      allocation.y + boxOffsetY (),\n                                      getContentWidth(),\n                                      getContentHeight());\n         }\n\n         usedView->drawSimpleWrappedText (getStyle()->font, getStyle()->color,\n                             core::style::Color::SHADING_NORMAL,\n                             allocation.x + boxOffsetX (),\n                             allocation.y + boxOffsetY (),\n                             getContentWidth(), getContentHeight(), altText);\n\n         if (clippingView)\n            view->mergeClippingView (clippingView);\n      }\n      if (mapKey) {\n         clippingView = view->getClippingView (allocation.x + boxOffsetX (),\n                                               allocation.y + boxOffsetY (),\n                                               getContentWidth(),\n                                               getContentHeight());\n         mapList->drawMap(mapKey, clippingView, getStyle(),\n                          allocation.x + boxOffsetX (),\n                          allocation.y + boxOffsetY ());\n         view->mergeClippingView (clippingView);\n      }\n   }\n\n   /** TODO: draw selection */\n}\n\ncore::Iterator *Image::iterator (core::Content::Type mask, bool atEnd)\n{\n   //return new core::TextIterator (this, mask, atEnd, altText);\n   /** \\bug Not implemented. */\n   return new core::EmptyIterator (this, mask, atEnd);\n}\n\nvoid Image::setBuffer (core::Imgbuf *buffer, bool resize)\n{\n   core::Imgbuf *oldBuf = this->buffer;\n\n   // TODO: I hack this to 0 to avoid using stale values when viewing\n   // a single image (Isolate image), and when going back in the browser\n   // history - but this sucks because when going back to a cached page\n   // lastallocationx isn't updated \"properly\" like it was during the\n   // initial page load. Hmmm.\n   lastallocationx = 0;\n\n   if (wasAllocated () && needsResize () &&\n      getContentWidth () > 0 && getContentHeight () > 0) {\n      // Don't create a new buffer for the transition from alt text to img,\n      // and only scale when both dimensions are known.\n\n      bufWidth = getContentWidth ();\n      bufHeight = getContentHeight ();\n      this->buffer = buffer->getScaledBuf (bufWidth, bufHeight);\n   } else {\n      this->buffer = buffer;\n      bufWidth = buffer->getRootWidth ();\n      bufHeight = buffer->getRootHeight ();\n      buffer->ref ();\n   }\n   queueResize (0, true);\n\n   DBG_OBJ_ASSOC_CHILD (this->buffer);\n   DBG_OBJ_SET_NUM (\"bufWidth\", bufWidth);\n   DBG_OBJ_SET_NUM (\"bufHeight\", bufHeight);\n\n   if (oldBuf)\n      oldBuf->unref ();\n}\n\nvoid Image::drawRow (int row)\n{\n   core::Rectangle area;\n\n   assert (buffer != NULL);\n\n   buffer->getRowArea (row, &area);\n   if (area.width && area.height)\n      queueDrawArea (area.x + boxOffsetX (), area.y + boxOffsetY (), area.width,\n                     area.height);\n}\n\nvoid Image::finish ()\n{\n   // Nothing to do; images are always drawn line by line.\n}\n\nvoid Image::fatal ()\n{\n   // Could display an error.\n}\n\n\n/**\n * \\brief Sets image as server side image map.\n */\nvoid Image::setIsMap ()\n{\n   isMap = true;\n}\n\n\n/**\n * \\brief Sets image as client side image map.\n *\n * \"list\" is not owned by the image, the caller has to free it. \"key\"\n * is owned by the image, if it is used by the caller afterwards, a copy\n * should be passed.\n */\nvoid Image::setUseMap (ImageMapsList *list, object::Object *key)\n{\n   mapList = list;\n   if (mapKey && mapKey != key)\n      delete mapKey;\n   mapKey = key;\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/image.hh",
    "content": "#ifndef __DW_IMAGE_HH__\n#define __DW_IMAGE_HH__\n\n#include \"core.hh\"\n\nnamespace dw {\n\n/**\n * \\brief Represents a list of client-side image maps.\n *\n * All image maps of a HTML page (in the future, also image maps from\n * different HTML pages) are stored in a list, which is passed to the\n * image, so that it is possible to deal with maps, which are defined\n * after the image within the HTML page.\n *\n * Maps are referred by instances of object::Object. These keys are\n * typically URLs, so the type representing URLS should be derived from\n * object::Object.\n *\n * \\todo Some methods within the key class have to be implemented, this\n *       is not clear at this time.\n */\nclass ImageMapsList\n{\nprivate:\n   class ImageMap: public lout::object::Object {\n      private:\n         class ShapeAndLink: public lout::object::Object {\n         public:\n            core::Shape *shape;\n            int link;\n\n            ~ShapeAndLink () { if (shape) delete shape; };\n         };\n\n         lout::container::typed::List <ShapeAndLink> *shapesAndLinks;\n         int defaultLink;\n      public:\n         ImageMap ();\n         ~ImageMap ();\n\n         void draw (core::View *view, core::style::Style *style, int x, int y);\n         void add (core::Shape *shape, int link);\n         void setDefaultLink (int link) { defaultLink = link; };\n         int link (int x, int y);\n   };\n\n   lout::container::typed::HashTable <lout::object::Object, ImageMap>\n      *imageMaps;\n   ImageMap *currentMap;\n\npublic:\n   ImageMapsList ();\n   ~ImageMapsList ();\n\n   void startNewMap (lout::object::Object *key);\n   void addShapeToCurrentMap (core::Shape *shape, int link);\n   void setCurrentMapDefaultLink (int link);\n   void drawMap(lout::object::Object *key, core::View *view,\n                core::style::Style *style, int x, int y);\n   int link (lout::object::Object *key, int x, int y);\n};\n\n/**\n * \\brief Displays an instance of dw::core::Imgbuf.\n *\n * The dw::core::Imgbuf is automatically scaled, when needed, but dw::Image\n * does not keep a reference on the root buffer.\n *\n *\n * <h3>Signals</h3>\n *\n * For image maps, dw::Image uses the signals defined in\n * dw::core::Layout::LinkReceiver. For client side image maps, -1 is\n * passed for the coordinates, for server side image maps, the respective\n * coordinates are used. See section \"Image Maps\" below.\n *\n *\n * <h3>%Image Maps</h3>\n *\n * <h4>Client Side %Image Maps</h4>\n *\n * You must first create a list of image maps (dw::ImageMapList), which can\n * be used for multiple images. The caller is responsible for freeing the\n * dw::ImageMapList.\n *\n * Adding a map is done by dw::ImageMapsList::startNewMap. The key is an\n * instance of a sub class of object::Object. In the context of HTML, this is\n * a URL, which defines this map globally, by combining the URL of the\n * document, this map is defined in, with the value of the attribute \"name\" of\n * the \\<MAP\\> element, as a fragment.\n *\n * dw::ImageMapsList::addShapeToCurrentMap adds a shape to the current\n * map. The \\em link argument is a number, which is later passed to\n * the dw::core::Layout::LinkReceiver.\n *\n * This map list is then, together with the key for the image, passed to\n * dw::Image::setUseMap. For HTML, a URL with the value of the \"ismap\"\n * attribute of \\<IMG\\> should be used.\n *\n * dw::Image will search the correct map, when needed. If it is not found\n * at this time, but later defined, it will be found and used later. This is\n * the case, when an HTML \\<MAP\\> is defined below the \\<IMG\\> in the\n * document.\n *\n * Currently, only maps defined in the same document as the image may be\n * used, since the dw::ImageMapsList is stored in the HTML link block, and\n * contains only the image maps defined in the document.\n *\n * <h4>Server Side %Image Maps</h4>\n *\n * To use images for server side image maps, you must call\n * dw::Image::setIsMap, and the dw::Image::style must contain a valid link\n * (dw::core::style::Style::x_link). After this, motions and clicks are\n * delegated to dw::core::Layout::LinkReceiver.\n *\n * \\sa\\ref dw-images-and-backgrounds\n */\nclass Image: public core::Widget, public core::ImgRenderer\n{\nprivate:\n   char *altText;\n   core::Imgbuf *buffer;\n   int bufWidth, bufHeight;\n   int altTextWidth;\n   bool clicking;\n   int currLink;\n   ImageMapsList *mapList;\n   Object *mapKey;\n   bool isMap;\n\nprotected:\n   void sizeRequestSimpl (core::Requisition *requisition);\n   void getExtremesSimpl (core::Extremes *extremes);\n   void sizeAllocateImpl (core::Allocation *allocation);\n   void containerSizeChangedForChildren ();\n   \n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n\n   bool buttonPressImpl (core::EventButton *event);\n   bool buttonReleaseImpl (core::EventButton *event);\n   void enterNotifyImpl (core::EventCrossing *event);\n   void leaveNotifyImpl (core::EventCrossing *event);\n   bool motionNotifyImpl (core::EventMotion *event);\n   int contentX (core::MousePositionEvent *event);\n   int contentY (core::MousePositionEvent *event);\n\n   //core::Iterator *iterator (Content::Type mask, bool atEnd);\n\npublic:\n   static int CLASS_ID;\n\n   static int lastallocationx;\n\n   Image(const char *altText);\n   ~Image();\n\n   // For images, the minimal width is not well defined, and\n   // correction of the size makes not much sense.\n   virtual bool getAdjustMinWidth () { return false; }\n\n   core::Iterator *iterator (core::Content::Type mask, bool atEnd);\n\n   inline core::Imgbuf *getBuffer () { return buffer; }\n   void setBuffer (core::Imgbuf *buffer, bool resize = false);\n\n   void drawRow (int row);\n\n   void finish ();\n   void fatal ();\n\n   void setIsMap ();\n   void setUseMap (ImageMapsList *list, Object *key);\n\n   /* This is a hack for the perhaps frivolous feature of drawing image map\n    * shapes when there is no image to display. If the map is defined after\n    * an image using an image map, and the actual image data has not been\n    * loaded, tell the image to redraw.\n    */\n   void forceMapRedraw () { if (mapKey && ! buffer) queueDraw (); };\n};\n\n} // namespace dw\n\n#endif // __DW_IMAGE_HH__\n"
  },
  {
    "path": "dw/imgbuf.hh",
    "content": "#ifndef __DW_IMGBUF_HH__\n#define __DW_IMGBUF_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\n#include \"../lout/debug.hh\"\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief The platform independent interface for image buffers.\n *\n * %Image buffers depend on the platform (see \\ref dw-images-and-backgrounds),\n * but have this general, platform independent interface. The purpose of\n * an image buffer is\n *\n * <ol>\n * <li> storing the image data,\n * <li> handling scaled versions of this buffer, and\n * <li> drawing.\n * </ol>\n *\n * The latter must be done independently from the window.\n *\n * <h3>Creating</h3>\n *\n * %Image buffers are created by calling dw::core::Platform::createImgbuf.\n *\n * <h3>Storing %Image Data</h3>\n *\n * dw::core::Imgbuf supports five image types, which are listed in the table\n * below. The representation defines, how the colors are stored within\n * the data, which is passed to dw::core::Imgbuf::copyRow.\n *\n * <table>\n * <tr><th>Type (dw::core::Imgbuf::Type) <th>Bytes per\n *                                           Pixel <th>Representation\n * <tr><td>dw::core::Imgbuf::RGB           <td>3   <td>red, green, blue\n * <tr><td>dw::core::Imgbuf::RGBA          <td>4   <td>red, green, blue, alpha\n * <tr><td>dw::core::Imgbuf::GRAY          <td>1   <td>gray value\n * <tr><td>dw::core::Imgbuf::INDEXED       <td>1   <td>index to colormap\n * <tr><td>dw::core::Imgbuf::INDEXED_ALPHA <td>1    <td>index to colormap\n * </table>\n *\n * The last two types need a colormap, which is set by\n * dw::core::Imgbuf::setCMap, which must be called before\n * dw::core::Imgbuf::copyRow. This function expects the colors as 32 bit\n * unsigned integers, which have the format 0xrrbbgg (for indexed\n * images), or 0xaarrggbb (for indexed alpha), respectively.\n *\n *\n * <h3>Scaling</h3>\n *\n * The buffer with the original size, which was created by\n * dw::core::Platform::createImgbuf, is called root buffer. Imgbuf provides\n * the ability to scale buffers. Generally, both root buffers, as well as\n * scaled buffers, may be shared, memory management is done by reference\n * counters.\n *\n * Via dw::core::Imgbuf::getScaledBuf, you can retrieve a scaled buffer.\n * Generally, something like this must work always, in an efficient way:\n *\n * \\code\n * dw::core::Imgbuf *curBuf, *oldBuf;\n * int width, heigt,\n * // ...\n * oldBuf = curBuf;\n * curBuf = oldBuf->getScaledBuf(oldBuf, width, height);\n * oldBuf->unref();\n * \\endcode\n *\n * \\em oldBuf may both be a root buffer, or a scaled buffer.\n *\n * The root buffer keeps a list of all children, and all methods\n * operating on the image data (dw::core::Imgbuf::copyRow and\n * dw::core::Imgbuf::setCMap) are delegated to the scaled buffers, when\n * processed, and inherited, when a new scaled buffer is created. This\n * means, that they must only be performed for the root buffer.\n *\n * A possible implementation could be (dw::fltk::FltkImgbuf does it this way):\n *\n * <ul>\n * <li> If the method is called with an already scaled image buffer, this is\n *      delegated to the root buffer.\n *\n * <li> If the given size is the original size, the root buffer is\n *      returned, with an increased reference counter.\n *\n * <li> Otherwise, if this buffer has already been scaled to the given\n *      size, return this scaled buffer, with an increased reference\n *      counter.\n *\n * <li> Otherwise, return a new scaled buffer with reference counter 1.\n * </ul>\n *\n * Special care is to be taken, when the root buffer is not used anymore,\n * i.e. after dw::core::Imgbuf::unref the reference counter is 0, but there\n * are still scaled buffers. Since all methods operating on the image data\n * (dw::core::Imgbuf::copyRow and dw::core::Imgbuf::setCMap) are called for\n * the root buffer, the root buffer is still needed, and so must not be\n * deleted at this point. This is, how dw::fltk::FltkImgbuf solves this\n * problem:\n *\n * <ul>\n * <li> dw::fltk::FltkImgbuf::unref does, for root buffers, check, not only\n *      whether dw::fltk::FltkImgbuf::refCount is 0, but also, whether\n *      there are children left. When the latter is the case, the buffer\n *      is not deleted.\n *\n * <li> There is an additional check in dw::fltk::FltkImgbuf::detachScaledBuf,\n *      which deals with the case, that dw::fltk::FltkImgbuf::refCount is 0,\n *      and the last scaled buffer is removed.\n * </ul>\n *\n * In the following example:\n *\n * \\code\n * dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform ();\n * dw::core::Layout *layout = new dw::core::Layout (platform);\n *\n * dw::core::Imgbuf *rootbuf =\n *    layout->createImgbuf (dw::core::Imgbuf::RGB, 100, 100);\n * dw::core::Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50);\n * rootbuf->unref ();\n * scaledbuf->unref ();\n * \\endcode\n *\n * the root buffer is not deleted, when dw::core::Imgbuf::unref is called,\n * since a scaled buffer is left. After calling dw::core::Imgbuf::unref for\n * the scaled buffer, it is deleted, and after it, the root buffer.\n *\n * <h3>Drawing</h3>\n *\n * dw::core::Imgbuf provides no methods for drawing, instead, this is\n * done by the views (implementation of dw::core::View).\n *\n * There are two situations, when drawing is necessary:\n *\n * <ol>\n * <li> To react on expose events, the function dw::core::View::drawImage\n *      should be used, with the following parameters:\n *      <ul>\n *      <li> of course, the image buffer,\n *      <li> where the root of the image would be displayed (as \\em xRoot\n *           and \\em yRoot), and\n *      <li> the region within the image, which should be displayed (\\em x,\n *           \\em y, \\em width, \\em height).\n *      </ul>\n *\n * <li> When a row has been copied, it has to be drawn. To determine the\n *      area, which has to be drawn, the dw::core::Imgbuf::getRowArea\n *      should be used. The result can then passed\n *      to dw::core::View::drawImage.\n * </ol>\n *\n * \\sa \\ref dw-images-and-backgrounds\n */\nclass Imgbuf: public lout::object::Object, public lout::signal::ObservedObject\n{\npublic:\n   enum Type { RGB, RGBA, GRAY, INDEXED, INDEXED_ALPHA };\n\n   inline Imgbuf () {\n      DBG_OBJ_CREATE (\"dw::core::Imgbuf\");\n      DBG_OBJ_BASECLASS (lout::object::Object);\n      DBG_OBJ_BASECLASS (lout::signal::ObservedObject);\n   }\n\n   inline ~Imgbuf () {\n      DBG_OBJ_DELETE ();\n   }\n\n   /*\n    * Methods called from the image decoding\n    */\n\n   virtual void setCMap (int *colors, int num_colors) = 0;\n   virtual void copyRow (int row, const byte *data) = 0;\n   virtual void newScan () = 0;\n\n   /*\n    * Methods called from dw::Image\n    */\n\n   virtual Imgbuf* getScaledBuf (int width, int height) = 0;\n   virtual void getRowArea (int row, dw::core::Rectangle *area) = 0;\n   virtual int getRootWidth () = 0;\n   virtual int getRootHeight () = 0;\n\n\n   /**\n    * Creates an image buffer with same parameters (type, gamma etc.)\n    * except size.\n    */\n   virtual Imgbuf *createSimilarBuf (int width, int height) = 0;\n\n   /**\n    * Copies another image buffer into this image buffer.\n    */\n   virtual void copyTo (Imgbuf *dest, int xDestRoot, int yDestRoot,\n                        int xSrc, int ySrc, int widthSrc, int heightSrc) = 0;\n\n   /*\n    * Reference counting.\n    */\n\n   virtual void ref () = 0;\n   virtual void unref () = 0;\n\n   /**\n    * \\todo Comment\n    */\n   virtual bool lastReference () = 0;\n\n\n   /**\n    * \\todo Comment\n    */\n   virtual void setDeleteOnUnref (bool deleteOnUnref) = 0;\n\n   /**\n    * \\todo Comment\n    */\n   virtual bool isReferred () = 0;\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_IMGBUF_HH__\n"
  },
  {
    "path": "dw/imgrenderer.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2013 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"core.hh\"\n\nnamespace dw {\nnamespace core {\n\nusing namespace lout::container;\nusing namespace lout::object;\n\nvoid ImgRendererDist::setBuffer (core::Imgbuf *buffer, bool resize)\n{\n   for (typed::Iterator <TypedPointer <ImgRenderer> > it =\n           children->iterator (); it.hasNext (); ) {\n      TypedPointer <ImgRenderer> *tp = it.getNext ();\n      tp->getTypedValue()->setBuffer (buffer, resize);\n   }\n}\n\nvoid ImgRendererDist::drawRow (int row)\n{\n   for (typed::Iterator <TypedPointer <ImgRenderer> > it =\n           children->iterator (); it.hasNext (); ) {\n      TypedPointer <ImgRenderer> *tp = it.getNext ();\n      tp->getTypedValue()->drawRow (row);\n   }\n}\n\n\nvoid ImgRendererDist::finish ()\n{\n   for (typed::Iterator <TypedPointer <ImgRenderer> > it =\n           children->iterator (); it.hasNext (); ) {\n      TypedPointer <ImgRenderer> *tp = it.getNext ();\n      tp->getTypedValue()->finish ();\n   }\n}\n\nvoid ImgRendererDist::fatal ()\n{\n   for (typed::Iterator <TypedPointer <ImgRenderer> > it =\n           children->iterator (); it.hasNext (); ) {\n      TypedPointer <ImgRenderer> *tp = it.getNext ();\n      tp->getTypedValue()->fatal ();\n   }\n}\n\n\n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/imgrenderer.hh",
    "content": "#ifndef __DW_IMGRENDERER_HH__\n#define __DW_IMGRENDERER_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief ...\n *\n * \\sa \\ref dw-images-and-backgrounds\n */\nclass ImgRenderer\n{\npublic:\n   virtual ~ImgRenderer () { }\n\n   /**\n    * \\brief Called, when an image buffer is attached.\n    *\n    * This is typically the case when all meta data (size, depth) has been read.\n    */\n   virtual void setBuffer (core::Imgbuf *buffer, bool resize = false) = 0;\n\n   /**\n    * \\brief Called, when data from a row is available and has been copied into\n    *    the image buffer.\n    *\n    * The implementation will typically queue the respective area for drawing.\n    */\n   virtual void drawRow (int row) = 0;\n\n   /**\n    * \\brief Called, when all image data has been retrieved.\n    *\n    * The implementation may use this instead of \"drawRow\" for drawing, to\n    * limit the number of draws.\n    */\n   virtual void finish () = 0;\n\n   /**\n    * \\brief Called, when there are problems with the retrieval of image data.\n    *\n    * The implementation may use this to indicate an error.\n    */\n   virtual void fatal () = 0;\n};\n\n/**\n * \\brief Implementation of ImgRenderer, which distributes all calls\n * to a set of other implementations of ImgRenderer.\n *\n * The order of the call children is not defined, especially not\n * identical to the order in which they have been added.\n */\nclass ImgRendererDist: public ImgRenderer\n{\n   lout::container::typed::HashSet <lout::object::TypedPointer <ImgRenderer> >\n      *children;\n\npublic:\n   inline ImgRendererDist ()\n   { children = new lout::container::typed::HashSet\n         <lout::object::TypedPointer <ImgRenderer> > (true); }\n   ~ImgRendererDist () { delete children; }\n\n   void setBuffer (core::Imgbuf *buffer, bool resize);\n   void drawRow (int row);\n   void finish ();\n   void fatal ();\n\n   void put (ImgRenderer *child)\n   { children->put (new lout::object::TypedPointer <ImgRenderer> (child)); }\n   void remove (ImgRenderer *child)\n   { lout::object::TypedPointer <ImgRenderer> tp (child);\n     children->remove (&tp); }\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_IMGRENDERER_HH__\n\n\n"
  },
  {
    "path": "dw/iterator.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"core.hh\"\n#include <limits.h>\n\nusing namespace lout;\n\nnamespace dw {\nnamespace core {\n\n// --------------\n//    Iterator\n// --------------\n\nIterator::Iterator(Widget *widget, Content::Type mask, bool atEnd)\n{\n   this->widget = widget;\n   this->mask = mask;\n}\n\nIterator::Iterator(Iterator &it): object::Comparable ()\n{\n   widget = it.widget;\n   content = it.content;\n}\n\nIterator::~Iterator()\n{\n}\n\nbool Iterator::equals (Object *other)\n{\n   Iterator *otherIt = (Iterator*)other;\n   return\n      this == otherIt ||\n      (getWidget() == otherIt->getWidget() && compareTo(otherIt) == 0);\n}\n\nvoid Iterator::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append (\"{ widget = \");\n   //widget->intoStringBuffer (sb);\n   sb->appendPointer (widget);\n   sb->append (\" (\");\n   sb->append (widget->getClassName());\n   sb->append (\")>\");\n\n   sb->append (\", mask = \");\n   Content::maskIntoStringBuffer (mask, sb);\n\n   sb->append (\", content = \");\n   Content::intoStringBuffer (&content, sb);\n\n   sb->append (\" }\");\n}\n\n/**\n * \\brief Delete the iterator.\n *\n * The destructor is hidden, implementations may use optimizations for\n * the allocation. (Will soon be the case for dw::core::EmptyIteratorFactory.)\n */\nvoid Iterator::unref ()\n{\n   delete this;\n}\n\n/**\n * \\brief Scrolls the viewport, so that the region between \\em it1 and\n * \\em it2 is seen, according to \\em hpos and \\em vpos.\n *\n * The parameters \\em start and \\em end have the same meaning as in\n * dw::core::Iterator::getAllocation, \\em start refers\n * to \\em it1, while \\em end rerers to \\em it2.\n *\n * If \\em it1 and \\em it2 point to the same location (see code), only\n * \\em it1 is regarded, and both belowstart and belowend refer to it.\n */\nvoid Iterator::scrollTo (Iterator *it1, Iterator *it2, int start, int end,\n                         HPosition hpos, VPosition vpos)\n{\n   Allocation alloc1, alloc2, alloc;\n   int x1, x2, y1, y2;\n   DeepIterator *eit1, *eit2, *eit3;\n   int curStart, curEnd, cmp;\n   bool atStart;\n\n   if (it1->equals(it2)) {\n      it1->getAllocation (start, end, &alloc);\n      it1->getWidget()->getLayout()->scrollTo (hpos, vpos, alloc.x, alloc.y,\n                                               alloc.width,\n                                               alloc.ascent + alloc.descent);\n   } else {\n      // First, determine the rectangle all iterators from it1 and it2\n      // allocate, i.e. the smallest rectangle containing all allocations of\n      // these iterators.\n      eit1 = new DeepIterator (it1);\n      eit2 = new DeepIterator (it2);\n\n      x1 = INT_MAX;\n      x2 = INT_MIN;\n      y1 = INT_MAX;\n      y2 = INT_MIN;\n\n      for (atStart = true, eit3 = (DeepIterator*)eit1->clone ();\n           (cmp = eit3->compareTo (eit2)) <= 0;\n           eit3->next (), atStart = false) {\n         if (atStart)\n            curStart = start;\n         else\n            curStart = 0;\n\n         if (cmp == 0)\n            curEnd = end;\n         else\n            curEnd = INT_MAX;\n\n         eit3->getAllocation (curStart, curEnd, &alloc);\n         x1 = misc::min (x1, alloc.x);\n         x2 = misc::max (x2, alloc.x + alloc.width);\n         y1 = misc::min (y1, alloc.y);\n         y2 = misc::max (y2, alloc.y + alloc.ascent + alloc.descent);\n      }\n\n      delete eit3;\n      delete eit2;\n      delete eit1;\n\n      it1->getAllocation (start, INT_MAX, &alloc1);\n      it2->getAllocation (0, end, &alloc2);\n\n      if (alloc1.x > alloc2.x) {\n         //\n         // This is due to a line break within the region. When the line is\n         // longer than the viewport, and the region is actually quite short,\n         // the user would not see anything of the region, as in this figure\n         // (with region marked as \"#\"):\n         //\n         //            +----------+   ,-- alloc1\n         //            |          |   V\n         //            |          |  ### ###\n         //   ### ###  |          |\n         //        ^   |          | <-- viewport\n         //        |   +----------+\n         //        `-- alloc2\n         //   |----------------------------|\n         //               width\n         //\n         // Therefore, we make the region smaller, so that the region will be\n         // displayed like this:\n         //\n         //                           ,-- alloc1\n         //                      +----|-----+\n         //                      |    V     |\n         //                      |   ### ###|\n         //   ### ###            |          |\n         //        ^             |          | <-- viewport\n         //        `-- alloc2    +----------+\n         //                      |----------|\n         //                         width\n         //\n\n         /** \\todo Changes in the viewport size, until the idle function is\n          *      called, are not regarded. */\n\n         if (it1->getWidget()->getLayout()->getUsesViewport() &&\n             x2 - x1 > it1->getWidget()->getLayout()->getWidthViewport()) {\n            x1 = x2 - it1->getWidget()->getLayout()->getWidthViewport();\n            x2 = x1 + it1->getWidget()->getLayout()->getWidthViewport();\n         }\n      }\n\n      if (alloc1.y > alloc2.y) {\n         // This is similar to the case above, e.g. if the region ends in\n         // another table column.\n         if (it1->getWidget()->getLayout()->getUsesViewport() &&\n             y2 - y1 > it1->getWidget()->getLayout()->getHeightViewport()) {\n            y1 = y2 - it1->getWidget()->getLayout()->getHeightViewport();\n            y2 = y1 + it1->getWidget()->getLayout()->getHeightViewport();\n         }\n      }\n\n      it1->getWidget()->getLayout()->scrollTo (hpos, vpos,\n                                               x1, y1, x2 - x1, y2 - y1);\n   }\n}\n\n// -------------------\n//    EmptyIterator\n// -------------------\n\nEmptyIterator::EmptyIterator (Widget *widget, Content::Type mask, bool atEnd):\n   Iterator (widget, mask, atEnd)\n{\n   this->content.type = (atEnd ? Content::END : Content::START);\n}\n\nEmptyIterator::EmptyIterator (EmptyIterator &it): Iterator (it)\n{\n}\n\nobject::Object *EmptyIterator::clone ()\n{\n   return new EmptyIterator (*this);\n}\n\nint EmptyIterator::compareTo (object::Comparable *other)\n{\n   EmptyIterator *otherIt = (EmptyIterator*)other;\n\n   if (content.type == otherIt->content.type)\n      return 0;\n   else if (content.type == Content::START)\n      return -1;\n   else\n      return +1;\n}\n\nbool EmptyIterator::next ()\n{\n   content.type = Content::END;\n   return false;\n}\n\nbool EmptyIterator::prev ()\n{\n   content.type = Content::START;\n   return false;\n}\n\nvoid EmptyIterator::highlight (int start, int end, HighlightLayer layer)\n{\n}\n\nvoid EmptyIterator::unhighlight (int direction, HighlightLayer layer)\n{\n}\n\nvoid EmptyIterator::getAllocation (int start, int end, Allocation *allocation)\n{\n}\n\n// ------------------\n//    TextIterator\n// ------------------\n\nTextIterator::TextIterator (Widget *widget, Content::Type mask, bool atEnd,\n                            const char *text): Iterator (widget, mask, atEnd)\n{\n   this->content.type = (atEnd ? Content::END : Content::START);\n   this->text = (mask & Content::TEXT) ? text : NULL;\n}\n\nTextIterator::TextIterator (TextIterator &it): Iterator (it)\n{\n   text = it.text;\n}\n\nint TextIterator::compareTo (object::Comparable *other)\n{\n   TextIterator *otherIt = (TextIterator*)other;\n\n   if (content.type == otherIt->content.type)\n      return 0;\n\n   switch (content.type) {\n   case Content::START:\n      return -1;\n\n   case Content::TEXT:\n      if (otherIt->content.type == Content::START)\n         return +1;\n      else\n         return -1;\n\n   case Content::END:\n      return +1;\n\n   default:\n      misc::assertNotReached();\n      return 0;\n   }\n}\n\nbool TextIterator::next ()\n{\n   if (content.type == Content::START && text != NULL) {\n      content.type = Content::TEXT;\n      content.text = text;\n      return true;\n   } else {\n      content.type = Content::END;\n      return false;\n   }\n}\n\nbool TextIterator::prev ()\n{\n   if (content.type == Content::END && text != NULL) {\n      content.type = Content::TEXT;\n      content.text = text;\n      return true;\n   } else {\n      content.type = Content::START;\n      return false;\n   }\n}\n\nvoid TextIterator::getAllocation (int start, int end, Allocation *allocation)\n{\n   // Return the allocation of the widget.\n   *allocation = *(getWidget()->getAllocation ());\n}\n\n// ------------------\n//    DeepIterator\n// ------------------\n\nDeepIterator::Stack::~Stack ()\n{\n   for (int i = 0; i < size (); i++)\n      get(i)->unref ();\n}\n\n/*\n * The following two methods are used by dw::core::DeepIterator::DeepIterator,\n * when the passed dw::core::Iterator points to a widget. Since a\n * dw::core::DeepIterator never returns a widget, the dw::core::Iterator has\n * to be corrected, by searching for the next content downwards (within the\n * widget pointed to), forwards, and backwards (in the traversed tree).\n */\n\n/*\n * Search downwards. If fromEnd is true, start search at the end,\n * otherwise at the beginning.\n */\nIterator *DeepIterator::searchDownward (Iterator *it, Content::Type mask,\n                                        bool fromEnd)\n{\n   Iterator *it2, *it3;\n\n   //DEBUG_MSG (1, \"%*smoving down (%swards) from %s\\n\",\n   //          indent, \"\", from_end ? \"back\" : \"for\", a_Dw_iterator_text (it));\n\n   assert (it->getContent()->type & Content::ANY_WIDGET);\n   it2 = it->getContent()->getWidget()->iterator (mask, fromEnd);\n\n   if (it2 == NULL) {\n      // Moving downwards failed.\n      //DEBUG_MSG (1, \"%*smoving down failed\\n\", indent, \"\");\n      return NULL;\n   }\n\n   while (fromEnd ? it2->prev () : it2->next ()) {\n      //DEBUG_MSG (1, \"%*sexamining %s\\n\",\n      //           indent, \"\", a_Dw_iterator_text (it2));\n\n      if (it2->getContent()->type & Content::ANY_WIDGET) {\n         // Another widget. Search in it downwards.\n         it3 = searchDownward (it2, mask, fromEnd);\n         if (it3 != NULL) {\n            it2->unref ();\n            return it3;\n         }\n         // Else continue in this widget.\n      } else {\n         // Success!\n         //DEBUG_MSG (1, \"%*smoving down succeeded: %s\\n\",\n         //           indent, \"\", a_Dw_iterator_text (it2));\n         return it2;\n      }\n   }\n\n   // Nothing found.\n   it2->unref ();\n   //DEBUG_MSG (1, \"%*smoving down failed (nothing found)\\n\", indent, \"\");\n   return NULL;\n}\n\n/*\n * Search sidewards. fromEnd specifies the direction, false means forwards,\n * true means backwards.\n */\nIterator *DeepIterator::searchSideward (Iterator *it, Content::Type mask,\n                                        bool fromEnd)\n{\n   Iterator *it2, *it3;\n\n   //DEBUG_MSG (1, \"%*smoving %swards from %s\\n\",\n   //          indent, \"\", from_end ? \"back\" : \"for\", a_Dw_iterator_text (it));\n\n   assert (it->getContent()->type & Content::ANY_WIDGET);\n   it2 = it->cloneIterator ();\n\n   while (fromEnd ? it2->prev () : it2->next ()) {\n      if (it2->getContent()->type & Content::ANY_WIDGET) {\n         // Search downwards in this widget.\n         it3 = searchDownward (it2, mask, fromEnd);\n         if (it3 != NULL) {\n            it2->unref ();\n            //DEBUG_MSG (1, \"%*smoving %swards succeeded: %s\\n\",\n            //           indent, \"\", from_end ? \"back\" : \"for\",\n            //           a_Dw_iterator_text (it3));\n            return it3;\n         }\n         // Else continue in this widget.\n      } else {\n         // Success!\n         // DEBUG_MSG (1, \"%*smoving %swards succeeded: %s\\n\",\n         //            indent, \"\", from_end ? \"back\" : \"for\",\n         //            a_Dw_iterator_text (it2));\n         return it2;\n      }\n   }\n\n   /* Nothing found, go upwards in the tree (if possible). */\n   it2->unref ();\n   Widget *respParent = getRespectiveParent (it->getWidget(), it->getMask());\n   if (respParent) {\n      it2 = respParent->iterator (mask, false);\n      while (true) {\n         if (!it2->next ())\n            misc::assertNotReached ();\n\n         if (it2->getContent()->type & Content::ANY_WIDGET &&\n             it2->getContent()->getWidget () == it->getWidget ()) {\n            it3 = searchSideward (it2, mask, fromEnd);\n            it2->unref ();\n            //DEBUG_MSG (1, \"%*smoving %swards succeeded: %s\\n\",\n            //           indent, \"\", from_end ? \"back\" : \"for\",\n            //           a_Dw_iterator_text (it3));\n            return it3;\n         }\n      }\n   }\n\n   // Nothing found at all.\n   // DEBUG_MSG (1, \"%*smoving %swards failed (nothing found)\\n\",\n   //            indent, \"\", from_end ? \"back\" : \"for\");\n   return NULL;\n}\n\nWidget *DeepIterator::getRespectiveParent (Widget *widget, Content::Type mask)\n{\n   // Return, depending on which is requested indirectly (follow\n   // references or containments) the parent (container) or the\n   // generator.  At this point, the type of the parent/generator is\n   // not known (since the parent/generator is not known), so we have\n   // to examine the mask. This is the reason why only one of\n   // WIDGET_OOF_CONT and WIDGET_OOF_REF is allowed.\n\n   return (mask & Content::WIDGET_OOF_REF) ?\n      widget->getGenerator() : widget->getParent();\n}\n\nint DeepIterator::getRespectiveLevel (Widget *widget, Content::Type mask)\n{\n   // Similar to getRespectiveParent.\n\n   return (mask & Content::WIDGET_OOF_REF) ?\n      widget->getGeneratorLevel() : widget->getLevel();\n}\n\n/**\n * \\brief Create a new deep iterator from an existing dw::core::Iterator.\n *\n * The content of the return value will be the content of \\em it. If within\n * the widget tree, there is no non-widget content, the resulting deep\n * iterator is empty (denoted by dw::core::DeepIterator::stack == NULL).\n *\n * Notes:\n *\n * <ol>\n * <li> The mask of \\em i\" must include DW_CONTENT_WIDGET, but\n *      dw::core::DeepIterator::next will never return widgets.\n * </ol>\n */\nDeepIterator::DeepIterator (Iterator *it)\n{\n   //printf (\"Starting creating DeepIterator %p ...\\n\", this);\n   //printf (\"Initial iterator: \");\n   //it->print ();\n   //printf (\"\\n\");\n\n   // Widgets out of flow are either followed widtin containers, or\n   // generators. Both (and also nothing at all) is not allowed. See\n   // also comment in getRespectiveParent.\n   int oofMask =\n      it->getMask() & (Content::WIDGET_OOF_CONT | Content::WIDGET_OOF_REF);\n   assert (oofMask == Content::WIDGET_OOF_CONT ||\n           oofMask == Content::WIDGET_OOF_REF);\n\n   //DEBUG_MSG (1, \"a_Dw_ext_iterator_new: %s\\n\", a_Dw_iterator_text (it));\n\n   // Clone input iterator, so the iterator passed as parameter\n   // remains untouched.\n   it = it->cloneIterator ();\n   this->mask = it->getMask ();\n\n   hasContents = true;\n\n   // If it points to a widget, find a near non-widget content,\n   // since an DeepIterator should never return widgets.\n   if (it->getContent()->type & Content::ANY_WIDGET) {\n      Iterator *it2;\n\n      // The second argument of searchDownward is actually a matter of\n      // taste :-)\n      if ((it2 = searchDownward (it, mask, false)) ||\n          (it2 = searchSideward (it, mask, false)) ||\n          (it2 = searchSideward (it, mask, true))) {\n         it->unref ();\n         it = it2;\n      } else {\n         // This may happen, when a page does not contain any non-widget\n         // content.\n         //DEBUG_MSG (1, \"a_Dw_ext_iterator_new got totally helpless!\\n\");\n         it->unref ();\n         hasContents = false;\n      }\n   }\n\n   //DEBUG_MSG (1, \"  => %s\\n\", a_Dw_iterator_text (it));\n\n   if (hasContents) {\n      // If this widget has parents, we must construct appropriate iterators.\n      //\n      // \\todo There may be a faster way instead of iterating through the\n      //    parent widgets.\n\n      //printf (\"Starting with: \");\n      //it->print ();\n      //printf (\"\\n\");\n\n      // Construct the iterators.\n      int thisLevel = getRespectiveLevel (it->getWidget()), level;\n      Widget *w;\n      for (w = it->getWidget (), level = thisLevel;\n           getRespectiveParent (w) != NULL;\n           w = getRespectiveParent (w), level--) {\n         Iterator *it = getRespectiveParent(w)->iterator (mask, false);\n\n         //printf (\"   parent: %s %p\\n\", w->getClassName (), w);\n\n         stack.put (it, level - 1);\n         while (true) {\n            //printf (\"         \");\n            //it->print ();\n            //printf (\"\\n\");\n\n            bool hasNext = it->next();\n            assert (hasNext);\n\n            if (it->getContent()->type & Content::ANY_WIDGET &&\n                it->getContent()->getWidget () == w)\n               break;\n         }\n\n         //printf (\"      %d: \", level - 1);\n         //it->print ();\n         //printf (\"\\n\");\n      }\n\n      stack.put (it, thisLevel);\n      content = *(it->getContent());\n   }\n\n   //printf (\"... done creating DeepIterator %p.\\n\", this);\n}\n\n\nDeepIterator::~DeepIterator ()\n{\n   //printf (\"Deleting DeepIterator %p ...\\n\", this);\n}\n\nobject::Object *DeepIterator::clone ()\n{\n   DeepIterator *it = new DeepIterator ();\n\n   for (int i = 0; i < stack.size (); i++)\n      it->stack.put (stack.get(i)->cloneIterator (), i);\n\n   it->mask = mask;\n   it->content = content;\n   it->hasContents = hasContents;\n\n   return it;\n}\n\nint DeepIterator::compareTo (object::Comparable *other)\n{\n   DeepIterator *otherDeepIterator = (DeepIterator*)other;\n\n   //printf (\"Compare: %s\\n\", stack.toString ());\n   //printf (\"     to: %s\\n\", otherDeepIterator->stack.toString ());\n\n   // Search the highest level, where the widgets are the same.\n   int level = 0;\n\n   // The Comparable interface does not define \"uncomparable\". Deep\n   // iterators are only comparable if they belong to the same widget\n   // tree, so have the same widget at the bottom at the\n   // stack. If this is not the case, we abort.\n\n   assert (stack.size() > 0);\n   assert (otherDeepIterator->stack.size() > 0);\n\n   //printf (\"Equal? The %s %p (of %p) and the %s %p (of %p)?\\n\",\n   //        stack.get(0)->getWidget()->getClassName(),\n   //        stack.get(0)->getWidget(), this,\n   //        otherDeepIterator->stack.get(0)->getWidget()->getClassName(),\n   //        otherDeepIterator->stack.get(0)->getWidget(), otherDeepIterator);\n\n   assert (stack.get(0)->getWidget()\n           == otherDeepIterator->stack.get(level)->getWidget());\n\n   while (stack.get(level)->getWidget ()\n          == otherDeepIterator->stack.get(level)->getWidget ()) {\n      if (level == stack.size() - 1 ||\n          level == otherDeepIterator->stack.size() - 1)\n         break;\n      level++;\n   }\n\n   //printf (\"      => level = %d (temorally)\\n\", level);\n\n   while (stack.get(level)->getWidget ()\n          != otherDeepIterator->stack.get(level)->getWidget ())\n      level--;\n\n   //printf (\"      => level = %d (finally)\\n\", level);\n\n   return stack.get(level)->compareTo (otherDeepIterator->stack.get(level));\n}\n\nDeepIterator *DeepIterator::createVariant(Iterator *it)\n{\n   /** \\todo Not yet implemented, and actually not yet needed very much. */\n   return new DeepIterator (it);\n}\n\nbool DeepIterator::isEmpty () {\n   return !hasContents;\n}\n\n/**\n * \\brief Move iterator forward and store content it.\n *\n * Returns true on success.\n */\nbool DeepIterator::next ()\n{\n   Iterator *it = stack.getTop ();\n\n   if (it->next ()) {\n      if (it->getContent()->type & Content::ANY_WIDGET) {\n         // Widget: new iterator on stack, to search in this widget.\n         stack.push (it->getContent()->getWidget()->iterator (mask, false));\n         return next ();\n      } else {\n         // Simply return the content of the iterartor.\n         content = *(it->getContent ());\n         return true;\n      }\n   } else {\n      // No more data in the top-most widget.\n      if (stack.size () > 1) {\n         // Pop iterator from stack, and move to next item in the old one.\n         stack.pop ();\n         return next ();\n      } else {\n         // Stack is empty.\n         content.type = Content::END;\n         return false;\n      }\n   }\n}\n\n/**\n * \\brief Move iterator backward and store content it.\n *\n * Returns true on success.\n */\nbool DeepIterator::prev ()\n{\n   Iterator *it = stack.getTop ();\n\n   if (it->prev ()) {\n      if (it->getContent()->type & Content::ANY_WIDGET) {\n         // Widget: new iterator on stack, to search in this widget.\n         stack.push (it->getContent()->getWidget()->iterator (mask, true));\n         return prev ();\n      } else {\n         // Simply return the content of the iterartor.\n         content = *(it->getContent ());\n         return true;\n      }\n   } else {\n      // No more data in the top-most widget.\n      if (stack.size () > 1) {\n         // Pop iterator from stack, and move to next item in the old one.\n         stack.pop ();\n         return prev ();\n      } else {\n         // Stack is empty.\n         content.type = Content::START;\n         return false;\n      }\n   }\n}\n\n// -----------------\n//    CharIterator\n// -----------------\n\nCharIterator::CharIterator ()\n{\n   it = NULL;\n}\n\n/**\n * \\brief ...\n *\n * If followReferences is true, only the reference are followed, when\n * the container and generator for a widget is different. If false,\n * only the container is followed.\n */\nCharIterator::CharIterator (Widget *widget, bool followReferences)\n{\n   Iterator *i =\n      widget->iterator (Content::maskForSelection (followReferences), false);\n   it = new DeepIterator (i);\n   i->unref ();\n   ch = START;\n}\n\nCharIterator::~CharIterator ()\n{\n   if (it)\n      delete it;\n}\n\nobject::Object *CharIterator::clone()\n{\n   CharIterator *cloned = new CharIterator ();\n   cloned->it = it->cloneDeepIterator ();\n   cloned->ch = ch;\n   cloned->pos = pos;\n   return cloned;\n}\n\nint CharIterator::compareTo(object::Comparable *other)\n{\n   CharIterator *otherIt = (CharIterator*)other;\n   int c = it->compareTo(otherIt->it);\n   if (c != 0)\n      return c;\n   else\n      return pos - otherIt->pos;\n}\n\nbool CharIterator::next ()\n{\n   if (ch == START || it->getContent()->type == Content::BREAK ||\n       (it->getContent()->type == Content::TEXT &&\n        it->getContent()->text[pos] == 0)) {\n      if (it->next()) {\n         if (it->getContent()->type == Content::BREAK)\n            ch = '\\n';\n         else { // if (it->getContent()->type == Content::TEXT)\n            pos = 0;\n            ch = it->getContent()->text[pos];\n            if (ch == 0)\n               // should not happen, actually\n               return next ();\n         }\n         return true;\n      }\n      else {\n         ch = END;\n         return false;\n      }\n   } else if (ch == END)\n      return false;\n   else {\n      // at this point, it->getContent()->type == Content::TEXT\n      pos++;\n      ch = it->getContent()->text[pos];\n      if (ch == 0) {\n         if (it->getContent()->space) {\n            ch = ' ';\n         } else {\n            return next ();\n         }\n      }\n\n      return true;\n   }\n}\n\nbool CharIterator::prev ()\n{\n   if (ch == END || it->getContent()->type == Content::BREAK ||\n       (it->getContent()->type == Content::TEXT && pos == 0)) {\n      if (it->prev()) {\n         if (it->getContent()->type == Content::BREAK)\n            ch = '\\n';\n         else { // if (it->getContent()->type == Content::TEXT)\n            if (it->getContent()->text[0] == 0)\n               return prev ();\n            else {\n               pos = strlen (it->getContent()->text);\n               if (it->getContent()->space) {\n                  ch = ' ';\n               } else {\n                  pos--;\n                  ch = it->getContent()->text[pos];\n               }\n            }\n         }\n         return true;\n      }\n      else {\n         ch = START;\n         return false;\n      }\n   } else if (ch == START)\n      return false;\n   else {\n      // at this point, it->getContent()->type == Content::TEXT\n      pos--;\n      ch = it->getContent()->text[pos];\n      return true;\n   }\n}\n\nvoid CharIterator::highlight (CharIterator *it1, CharIterator *it2,\n                              HighlightLayer layer)\n{\n   if (it2->getChar () == CharIterator::END)\n      it2->prev ();\n\n   if (it1->it->compareTo (it2->it) == 0)\n      // Only one content => highlight part of it.\n      it1->it->highlight (it1->pos, it2->pos, layer);\n   else {\n      DeepIterator *it = it1->it->cloneDeepIterator ();\n      int c;\n      bool start;\n      for (start = true;\n           (c = it->compareTo (it2->it)) <= 0;\n           it->next (), start = false) {\n         int endOfWord =\n            it->getContent()->type == Content::TEXT ?\n            strlen (it->getContent()->text) : 1;\n         if (start) // first iteration\n            it->highlight (it1->pos, endOfWord, layer);\n         else if (c == 0) // last iteration\n            it->highlight (0, it2->pos, layer);\n         else\n            it->highlight (0, endOfWord, layer);\n      }\n      delete it;\n   }\n}\n\nvoid CharIterator::unhighlight (CharIterator *it1, CharIterator *it2,\n                                HighlightLayer layer)\n{\n   if (it1->it->compareTo (it2->it) == 0)\n      // Only one content => unhighlight it (only for efficiency).\n      it1->it->unhighlight (0, layer);\n   else {\n      DeepIterator *it = it1->it->cloneDeepIterator ();\n      for (; it->compareTo (it2->it) <= 0; it->next ())\n         it->unhighlight (-1, layer);\n      delete it;\n   }\n}\n\n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/iterator.hh",
    "content": "#ifndef __ITERATOR_HH__\n#define __ITERATOR_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief Iterators are used to iterate through the contents of a widget.\n *\n * When using iterators, you should care about the results of\n * dw::core::Widget::hasContents.\n *\n * \\sa dw::core::Widget::iterator\n */\nclass Iterator: public lout::object::Comparable\n{\nprotected:\n   Iterator(Widget *widget, Content::Type mask, bool atEnd);\n   Iterator(Iterator &it);\n   ~Iterator();\n\n   Content content;\n\nprivate:\n   Widget *widget;\n   Content::Type mask;\n\npublic:\n   bool equals (Object *other);\n   void intoStringBuffer(lout::misc::StringBuffer *sb);\n\n   inline Widget *getWidget () { return widget; }\n   inline Content *getContent () { return &content; }\n   inline Content::Type getMask () { return mask; }\n\n   virtual void unref ();\n\n   /**\n    * \\brief Move iterator forward and store content it.\n    *\n    * Returns true on success.\n    */\n   virtual bool next () = 0;\n\n   /**\n    * \\brief Move iterator backward and store content it.\n    *\n    * Returns true on success.\n    */\n   virtual bool prev () = 0;\n\n   /**\n    * \\brief Extend highlighted region to contain part of the current content.\n    *\n    * For text, start and end define the\n    * characters, otherwise, the shape is defined as [0, 1], i.e. for\n    * highlighting a whole dw::core::Content, pass 0 and >= 1.\n    * To unhighlight see also dw::core::Iterator::unhighlight.\n    */\n   virtual void highlight (int start, int end, HighlightLayer layer) = 0;\n\n   /**\n    * \\brief Shrink highlighted region to no longer contain the\n    *    current content.\n    *\n    * The direction parameter indicates whether the highlighted region should\n    * be reduced from the start (direction > 0) or from the end\n    * (direction < 0). If direction is 0 all content is unhighlighted.\n    */\n   virtual void unhighlight (int direction, HighlightLayer layer) = 0;\n\n   /**\n    * \\brief Return the shape, which a part of the item, the iterator points\n    *    on, allocates.\n    *\n    * The parameters start and end have the same meaning as in\n    * DwIterator::highlight().\n    */\n   virtual void getAllocation (int start, int end, Allocation *allocation) = 0;\n\n   inline Iterator *cloneIterator () { return (Iterator*)clone(); }\n\n   static void scrollTo (Iterator *it1, Iterator *it2, int start, int end,\n                         HPosition hpos, VPosition vpos);\n};\n\n\n/**\n * \\brief This implementation of dw::core::Iterator can be used by widgets\n *    with no contents.\n */\nclass EmptyIterator: public Iterator\n{\nprivate:\n   EmptyIterator (EmptyIterator &it);\n\npublic:\n   EmptyIterator (Widget *widget, Content::Type mask, bool atEnd);\n\n   lout::object::Object *clone();\n   int compareTo(lout::object::Comparable *other);\n   bool next ();\n   bool prev ();\n   void highlight (int start, int end, HighlightLayer layer);\n   void unhighlight (int direction, HighlightLayer layer);\n   void getAllocation (int start, int end, Allocation *allocation);\n};\n\n\n/**\n * \\brief This implementation of dw::core::Iterator can be used by widgets\n *    having one text word as contents\n */\nclass TextIterator: public Iterator\n{\nprivate:\n   /** May be NULL, in this case, the next is skipped. */\n   const char *text;\n\n   TextIterator (TextIterator &it);\n\npublic:\n   TextIterator (Widget *widget, Content::Type mask, bool atEnd,\n                 const char *text);\n\n   int compareTo(lout::object::Comparable *other);\n\n   bool next ();\n   bool prev ();\n   void getAllocation (int start, int end, Allocation *allocation);\n};\n\n\n/**\n * \\brief A stack of iterators, to iterate recursively through a widget tree.\n *\n * This class is similar to dw::core::Iterator, but not\n * created by a widget, but explicitly from another iterator. Deep\n * iterators do not have the limitation, that iteration is only done within\n * a widget, instead, child widgets are iterated through recursively.\n */\nclass DeepIterator: public lout::object::Comparable\n{\nprivate:\n   class Stack: public lout::container::typed::Vector<Iterator>\n   {\n   public:\n      inline Stack (): lout::container::typed::Vector<Iterator> (4, false) { }\n      ~Stack ();\n      inline Iterator *getTop () { return get (size () - 1); }\n      inline void push (Iterator *it) { put(it, -1); }\n      inline void pop() { getTop()->unref (); remove (size () - 1); }\n   };\n\n   Stack stack;\n\n   static Iterator *searchDownward (Iterator *it, Content::Type mask,\n                                    bool fromEnd);\n   static Iterator *searchSideward (Iterator *it, Content::Type mask,\n                                    bool fromEnd);\n\n   Content::Type mask;\n   Content content;\n   bool hasContents;\n\n   inline DeepIterator () { }\n\n   static Widget *getRespectiveParent (Widget *widget, Content::Type mask);\n   inline Widget *getRespectiveParent (Widget *widget) {\n      return getRespectiveParent (widget, mask);\n   }\n\n   static int getRespectiveLevel (Widget *widget, Content::Type mask);\n   inline int getRespectiveLevel (Widget *widget) {\n      return getRespectiveLevel (widget, mask);\n   }\n\npublic:\n   DeepIterator(Iterator *it);\n   ~DeepIterator();\n\n   lout::object::Object *clone ();\n\n   DeepIterator *createVariant(Iterator *it);\n   inline Iterator *getTopIterator () { return stack.getTop(); }\n   inline Content *getContent () { return &content; }\n\n   bool isEmpty ();\n\n   bool next ();\n   bool prev ();\n   inline DeepIterator *cloneDeepIterator() { return (DeepIterator*)clone(); }\n   int compareTo(lout::object::Comparable *other);\n\n   /**\n    * \\brief Highlight a part of the current content.\n    *\n    * Unhighlight the current content by passing -1 as start (see also\n    * (dw::core::Iterator::unhighlight). For text, start and end define the\n    * characters, otherwise, the shape is defined as [0, 1], i.e. for\n    * highlighting a whole dw::core::Content, pass 0 and >= 1.\n    */\n   inline void highlight (int start, int end, HighlightLayer layer)\n   { stack.getTop()->highlight (start, end, layer); }\n\n   /**\n    * \\brief Return the shape, which a part of the item, the iterator points\n    *    on, allocates.\n    *\n    * The parameters start and end have the same meaning as in\n    * DwIterator::highlight().\n    */\n   inline void getAllocation (int start, int end, Allocation *allocation)\n   { stack.getTop()->getAllocation (start, end, allocation); }\n\n   inline void unhighlight (int direction, HighlightLayer layer)\n   { stack.getTop()->unhighlight (direction, layer); }\n\n   inline static void scrollTo (DeepIterator *it1, DeepIterator *it2,\n                                int start, int end,\n                                HPosition hpos, VPosition vpos)\n   { Iterator::scrollTo(it1->stack.getTop(), it2->stack.getTop(),\n                         start, end, hpos, vpos); }\n};\n\nclass CharIterator: public lout::object::Comparable\n{\npublic:\n   // START and END must not clash with any char value\n   // neither for signed nor unsigned char.\n   enum { START = 257, END = 258 };\n\nprivate:\n   DeepIterator *it;\n   int pos, ch;\n\n   CharIterator ();\n\npublic:\n   CharIterator (Widget *widget, bool followReferences);\n   ~CharIterator ();\n\n   lout::object::Object *clone();\n   int compareTo(lout::object::Comparable *other);\n\n   bool next ();\n   bool prev ();\n   inline int getChar() { return ch; }\n   inline CharIterator *cloneCharIterator() { return (CharIterator*)clone(); }\n\n   static void highlight (CharIterator *it1, CharIterator *it2,\n                          HighlightLayer layer);\n   static void unhighlight (CharIterator *it1, CharIterator *it2,\n                            HighlightLayer layer);\n\n   inline static void scrollTo (CharIterator *it1, CharIterator *it2,\n                                HPosition hpos, VPosition vpos)\n   { DeepIterator::scrollTo(it1->it, it2->it, it1->pos, it2->pos,\n                            hpos, vpos); }\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __ITERATOR_HH__\n"
  },
  {
    "path": "dw/layout.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"core.hh\"\n\n#include \"../lout/msg.h\"\n#include \"../lout/debug.hh\"\n#include \"../lout/misc.hh\"\n\nusing namespace lout;\nusing namespace lout::container;\nusing namespace lout::object;\n\nnamespace dw {\nnamespace core {\n\nbool Layout::LayoutImgRenderer::readyToDraw ()\n{\n   return true;\n}\n\nvoid Layout::LayoutImgRenderer::getBgArea (int *x, int *y, int *width,\n                                           int *height)\n{\n   // TODO Actually not padding area, but visible area?\n   getRefArea (x, y, width, height);\n}\n\nvoid Layout::LayoutImgRenderer::getRefArea (int *xRef, int *yRef, int *widthRef,\n                                            int *heightRef)\n{\n   *xRef = 0;\n   *yRef = 0;\n   *widthRef = misc::max (layout->viewportWidth\n                          - (layout->canvasHeightGreater ?\n                             layout->vScrollbarThickness : 0),\n                          layout->canvasWidth);\n   *heightRef = misc::max (layout->viewportHeight\n                           - layout->hScrollbarThickness,\n                           layout->canvasAscent + layout->canvasDescent);\n}\n\nstyle::StyleImage *Layout::LayoutImgRenderer::getBackgroundImage ()\n{\n   return layout->bgImage;\n}\n\nstyle::BackgroundRepeat Layout::LayoutImgRenderer::getBackgroundRepeat ()\n{\n   return layout->bgRepeat;\n}\n\nstyle::BackgroundAttachment\n   Layout::LayoutImgRenderer::getBackgroundAttachment ()\n{\n   return layout->bgAttachment;\n}\n\nstyle::Length Layout::LayoutImgRenderer::getBackgroundPositionX ()\n{\n   return layout->bgPositionX;\n}\n\nstyle::Length Layout::LayoutImgRenderer::getBackgroundPositionY ()\n{\n   return layout->bgPositionY;\n}\n\nvoid Layout::LayoutImgRenderer::draw (int x, int y, int width, int height)\n{\n   layout->queueDraw (x, y, width, height);\n}\n\n// ----------------------------------------------------------------------\n\nvoid Layout::Receiver::resizeQueued (bool extremesChanged)\n{\n}\n\nvoid Layout::Receiver::canvasSizeChanged (int width, int ascent, int descent)\n{\n}\n\n// ----------------------------------------------------------------------\n\nbool Layout::Emitter::emitToReceiver (lout::signal::Receiver *receiver,\n                                      int signalNo, int argc,\n                                      lout::object::Object **argv)\n{\n   Receiver *layoutReceiver = (Receiver*)receiver;\n\n   switch (signalNo) {\n   case CANVAS_SIZE_CHANGED:\n      layoutReceiver->canvasSizeChanged (((Integer*)argv[0])->getValue (),\n                                         ((Integer*)argv[1])->getValue (),\n                                         ((Integer*)argv[2])->getValue ());\n      break;\n\n   case RESIZE_QUEUED:\n      layoutReceiver->resizeQueued (((Boolean*)argv[0])->getValue ());\n      break;\n\n   default:\n      misc::assertNotReached ();\n   }\n\n   return false;\n}\n\nvoid Layout::Emitter::emitResizeQueued (bool extremesChanged)\n{\n   Boolean ec (extremesChanged);\n   Object *argv[1] = { &ec };\n   emitVoid (RESIZE_QUEUED, 1, argv);\n}\n\nvoid Layout::Emitter::emitCanvasSizeChanged (int width,\n                                             int ascent, int descent)\n{\n   Integer w (width), a (ascent), d (descent);\n   Object *argv[3] = { &w, &a, &d };\n   emitVoid (CANVAS_SIZE_CHANGED, 3, argv);\n}\n\n// ----------------------------------------------------------------------\n\nbool Layout::LinkReceiver::enter (Widget *widget, int link, int img,\n                                  int x, int y)\n{\n   return false;\n}\n\nbool Layout::LinkReceiver::press (Widget *widget, int link, int img,\n                                  int x, int y, EventButton *event)\n{\n   return false;\n}\n\nbool Layout::LinkReceiver::release (Widget *widget, int link, int img,\n                                    int x, int y, EventButton *event)\n{\n   return false;\n}\n\nbool Layout::LinkReceiver::click (Widget *widget, int link, int img,\n                                    int x, int y, EventButton *event)\n{\n   return false;\n}\n\n// ----------------------------------------------------------------------\n\nbool Layout::LinkEmitter::emitToReceiver (lout::signal::Receiver *receiver,\n                                          int signalNo, int argc,\n                                          lout::object::Object **argv)\n{\n   LinkReceiver *linkReceiver = (LinkReceiver*)receiver;\n\n   switch (signalNo) {\n   case ENTER:\n      return linkReceiver->enter ((Widget*)argv[0],\n                                  ((Integer*)argv[1])->getValue (),\n                                  ((Integer*)argv[2])->getValue (),\n                                  ((Integer*)argv[3])->getValue (),\n                                  ((Integer*)argv[4])->getValue ());\n\n   case PRESS:\n      return linkReceiver->press ((Widget*)argv[0],\n                                  ((Integer*)argv[1])->getValue (),\n                                  ((Integer*)argv[2])->getValue (),\n                                  ((Integer*)argv[3])->getValue (),\n                                  ((Integer*)argv[4])->getValue (),\n                                  (EventButton*)argv[5]);\n\n   case RELEASE:\n      return linkReceiver->release ((Widget*)argv[0],\n                                    ((Integer*)argv[1])->getValue (),\n                                    ((Integer*)argv[2])->getValue (),\n                                    ((Integer*)argv[3])->getValue (),\n                                    ((Integer*)argv[4])->getValue (),\n                                    (EventButton*)argv[5]);\n\n   case CLICK:\n      return linkReceiver->click ((Widget*)argv[0],\n                                  ((Integer*)argv[1])->getValue (),\n                                  ((Integer*)argv[2])->getValue (),\n                                  ((Integer*)argv[3])->getValue (),\n                                  ((Integer*)argv[4])->getValue (),\n                                  (EventButton*)argv[5]);\n\n   default:\n      misc::assertNotReached ();\n   }\n   return false;\n}\n\nbool Layout::LinkEmitter::emitEnter (Widget *widget, int link, int img,\n                                     int x, int y)\n{\n   Integer ilink (link), iimg (img), ix (x), iy (y);\n   Object *argv[5] = { widget, &ilink, &iimg, &ix, &iy };\n   return emitBool (ENTER, 5, argv);\n}\n\nbool Layout::LinkEmitter::emitPress (Widget *widget, int link, int img,\n                                     int x, int y, EventButton *event)\n{\n   Integer ilink (link), iimg (img), ix (x), iy (y);\n   Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };\n   return emitBool (PRESS, 6, argv);\n}\n\nbool Layout::LinkEmitter::emitRelease (Widget *widget, int link, int img,\n                                       int x, int y, EventButton *event)\n{\n   Integer ilink (link), iimg (img), ix (x), iy (y);\n   Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };\n   return emitBool (RELEASE, 6, argv);\n}\n\nbool Layout::LinkEmitter::emitClick (Widget *widget, int link, int img,\n                                     int x, int y, EventButton *event)\n{\n   Integer ilink (link), iimg (img), ix (x), iy (y);\n   Object *argv[6] = { widget, &ilink, &iimg, &ix, &iy, event };\n   return emitBool (CLICK, 6, argv);\n}\n\n// ---------------------------------------------------------------------\n\nLayout::Anchor::~Anchor ()\n{\n   free(name);\n}\n\n// ---------------------------------------------------------------------\n\nLayout::Layout (Platform *platform)\n{\n   this->platform = platform;\n   view = NULL;\n   topLevel = NULL;\n   widgetAtPoint = NULL;\n\n   queueResizeList = new typed::Vector<Widget> (4, false);\n\n   DBG_OBJ_CREATE (\"dw::core::Layout\");\n\n   bgColor = NULL;\n   bgImage = NULL;\n   cursor = style::CURSOR_DEFAULT;\n\n   canvasWidth = canvasAscent = canvasDescent = 0;\n\n   usesViewport = false;\n   drawAfterScrollReq = false;\n   scrollX = scrollY = 0;\n   viewportWidth = viewportHeight = 0;\n   hScrollbarThickness = vScrollbarThickness = 0;\n\n   DBG_OBJ_SET_NUM (\"viewportWidth\", viewportWidth);\n   DBG_OBJ_SET_NUM (\"viewportHeight\", viewportHeight);\n   DBG_OBJ_SET_NUM (\"hScrollbarThickness\", hScrollbarThickness);\n   DBG_OBJ_SET_NUM (\"vScrollbarThickness\", vScrollbarThickness);\n\n   requestedAnchor = NULL;\n   scrollIdleId = -1;\n   scrollIdleNotInterrupted = false;\n\n   anchorsTable =\n      new container::typed::HashTable <object::String, Anchor> (true, true);\n\n   resizeIdleId = -1;\n\n   textZone = new misc::ZoneAllocator (16 * 1024);\n\n   DBG_OBJ_ASSOC_CHILD (&findtextState);\n   DBG_OBJ_ASSOC_CHILD (&selectionState);\n\n   platform->setLayout (this);\n\n   selectionState.setLayout(this);\n\n   queueResizeCounter = sizeAllocateCounter = sizeRequestCounter =\n      getExtremesCounter = 0;\n\n   layoutImgRenderer = NULL;\n\n   resizeIdleCounter = queueResizeCounter = sizeAllocateCounter\n      = sizeRequestCounter = getExtremesCounter = 0;\n}\n\nLayout::~Layout ()\n{\n   widgetAtPoint = NULL;\n\n   if (layoutImgRenderer) {\n      if (bgImage)\n         bgImage->removeExternalImgRenderer (layoutImgRenderer);\n      delete layoutImgRenderer;\n   }\n\n   if (scrollIdleId != -1)\n      platform->removeIdle (scrollIdleId);\n   if (resizeIdleId != -1)\n      platform->removeIdle (resizeIdleId);\n   if (bgColor)\n      bgColor->unref ();\n   if (bgImage)\n      bgImage->unref ();\n   if (topLevel) {\n      detachWidget (topLevel);\n      Widget *w = topLevel;\n      topLevel = NULL;\n      delete w;\n   }\n\n   delete queueResizeList;\n   delete platform;\n   delete view;\n   delete anchorsTable;\n   delete textZone;\n\n   if (requestedAnchor)\n      free (requestedAnchor);\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid Layout::detachWidget (Widget *widget)\n{\n   // Called form ~Layout. Sometimes, the widgets (not only the toplevel widget)\n   // do some stuff after the layout has been deleted, so *all* widgets have to\n   // be detached, and check \"layout != NULL\" at relevant points.\n\n   // Could be replaced by a virtual method in Widget, like getWidgetAtPoint,\n   // if performace were really a problem.\n\n   widget->layout = NULL;\n   Iterator *it =\n      widget->iterator ((Content::Type)\n                        (Content::WIDGET_IN_FLOW | Content::WIDGET_OOF_CONT),\n                        false);\n   while (it->next ())\n      detachWidget (it->getContent()->widget);\n\n   it->unref ();\n}\n\nvoid Layout::addWidget (Widget *widget)\n{\n   if (topLevel) {\n      MSG_WARN(\"widget already set\\n\");\n      return;\n   }\n\n   // The toplevel widget always establishes a stacking context. It could\n   // already be set in Widget::setStyle().\n   if (widget->stackingContextMgr == NULL && IMPL_POS) {\n      widget->stackingContextMgr = new StackingContextMgr (widget);\n      DBG_OBJ_ASSOC (widget, widget->stackingContextMgr);\n      widget->stackingContextWidget = widget;\n   }\n\n   topLevel = widget;\n   widget->layout = this;\n   widget->container = NULL;\n   DBG_OBJ_SET_PTR_O (widget, \"container\", widget->container);\n\n   queueResizeList->clear ();\n   widget->notifySetAsTopLevel ();\n\n   findtextState.setWidget (widget);\n\n   canvasHeightGreater = false;\n   DBG_OBJ_SET_SYM (\"canvasHeightGreater\",\n                    canvasHeightGreater ? \"true\" : \"false\");\n\n   // Do not directly call Layout::queueResize(), but\n   // Widget::queueResize(), so that all flags are set properly,\n   // queueResizeList is filled, etc.\n   topLevel->queueResize (-1, false);\n}\n\nvoid Layout::removeWidget ()\n{\n   /**\n    * \\bug Some more attributes must be reset here.\n    */\n   topLevel = NULL;\n   queueResizeList->clear ();\n   widgetAtPoint = NULL;\n   canvasWidth = canvasAscent = canvasDescent = 0;\n   scrollX = scrollY = 0;\n\n   view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);\n   if (view->usesViewport ())\n      view->setViewportSize (viewportWidth, viewportHeight, 0, 0);\n   view->queueDrawTotal ();\n\n   setAnchor (NULL);\n   updateAnchor ();\n\n   emitter.emitCanvasSizeChanged (canvasWidth, canvasAscent, canvasDescent);\n\n   findtextState.setWidget (NULL);\n   selectionState.reset ();\n\n   updateCursor ();\n}\n\nvoid Layout::setWidget (Widget *widget)\n{\n   DBG_OBJ_ASSOC_CHILD (widget);\n\n   widgetAtPoint = NULL;\n   if (topLevel) {\n      Widget *w = topLevel;\n      topLevel = NULL;\n      delete w;\n   }\n   textZone->zoneFree ();\n   addWidget (widget);\n\n   updateCursor ();\n}\n\n/**\n * \\brief Attach a view to the layout.\n *\n * It will become a child of the layout,\n * and so it will be destroyed, when the layout will be destroyed.\n */\nvoid Layout::attachView (View *view)\n{\n   if (this->view)\n      MSG_ERR(\"attachView: Multiple views for layout!\\n\");\n\n   DBG_OBJ_ASSOC_CHILD (view);\n\n   this->view = view;\n   platform->attachView (view);\n\n   /*\n    * The layout of the view is set later, first, we \"project\" the current\n    * state of the layout into the new view. A view must handle this without\n    * a layout. See also at the end of this function.\n    */\n   if (bgColor)\n      view->setBgColor (bgColor);\n   view->setCursor (cursor);\n   view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);\n\n   if (view->usesViewport ()) {\n      if (usesViewport) {\n         view->scrollTo (scrollX, scrollY);\n         view->setViewportSize (viewportWidth, viewportHeight,\n                                hScrollbarThickness, vScrollbarThickness);\n         hScrollbarThickness = misc::max (hScrollbarThickness,\n                                          view->getHScrollbarThickness ());\n         vScrollbarThickness = misc::max (vScrollbarThickness,\n                                          view->getVScrollbarThickness ());\n      }\n      else {\n         usesViewport = true;\n         scrollX = scrollY = 0;\n         viewportWidth = viewportHeight = 100; // random values\n         hScrollbarThickness = view->getHScrollbarThickness ();\n         vScrollbarThickness = view->getVScrollbarThickness ();\n      }\n\n      DBG_OBJ_SET_NUM (\"viewportWidth\", viewportWidth);\n      DBG_OBJ_SET_NUM (\"viewportHeight\", viewportHeight);\n      DBG_OBJ_SET_NUM (\"hScrollbarThickness\", hScrollbarThickness);\n      DBG_OBJ_SET_NUM (\"vScrollbarThickness\", vScrollbarThickness);\n   }\n\n   /*\n    * This is the last call within this function, so that it is safe for\n    * the implementation of dw::core::View::setLayout, to call methods\n    * of dw::core::Layout.\n    */\n   view->setLayout (this);\n}\n\nvoid Layout::detachView (View *view)\n{\n   if (this->view != view)\n      MSG_ERR(\"detachView: this->view: %p view %p\\n\", this->view, view);\n\n   view->setLayout (NULL);\n   platform->detachView (view);\n   this->view = NULL;\n   /**\n    * \\todo Actually, viewportMarkerWidthDiff and\n    *       viewportMarkerHeightDiff have to be recalculated here, since the\n    *       effective (i.e. maximal) values may change, after the view has been\n    *       detached. Same applies to the usage of viewports.\n    */\n}\n\nvoid Layout::scroll(ScrollCommand cmd)\n{\n   if (view->usesViewport ())\n      view->scroll(cmd);\n}\n\n/**\n * \\brief Scrolls all viewports, so that the region [x, y, width, height]\n *    is seen, according to hpos and vpos.\n */\nvoid Layout::scrollTo (HPosition hpos, VPosition vpos,\n                       int x, int y, int width, int height)\n{\n   scrollTo0 (hpos, vpos, x, y, width, height, true);\n}\n\nvoid Layout::scrollTo0 (HPosition hpos, VPosition vpos,\n                        int x, int y, int width, int height,\n                        bool scrollingInterrupted)\n{\n   if (usesViewport) {\n      _MSG(\"scrollTo (%d, %d, %s)\\n\",\n           x, y, scrollingInterrupted ? \"true\" : \"false\");\n\n      scrollTargetHpos = hpos;\n      scrollTargetVpos = vpos;\n      scrollTargetX = x;\n      scrollTargetY = y;\n      scrollTargetWidth = width;\n      scrollTargetHeight = height;\n\n      if (scrollIdleId == -1) {\n         scrollIdleId = platform->addIdle (&Layout::scrollIdle);\n         scrollIdleNotInterrupted = true;\n      }\n\n      scrollIdleNotInterrupted =\n         scrollIdleNotInterrupted || !scrollingInterrupted;\n   }\n}\n\nvoid Layout::scrollIdle ()\n{\n   bool xChanged = true;\n   switch (scrollTargetHpos) {\n   case HPOS_LEFT:\n      scrollX = scrollTargetX;\n      break;\n   case HPOS_CENTER:\n      scrollX =\n         scrollTargetX\n         - (viewportWidth - currVScrollbarThickness() - scrollTargetWidth) / 2;\n      break;\n   case HPOS_RIGHT:\n      scrollX =\n         scrollTargetX\n         - (viewportWidth - currVScrollbarThickness() - scrollTargetWidth);\n      break;\n   case HPOS_INTO_VIEW:\n      xChanged = calcScrollInto (scrollTargetX, scrollTargetWidth, &scrollX,\n                                 viewportWidth - currVScrollbarThickness());\n      break;\n   case HPOS_NO_CHANGE:\n      xChanged = false;\n      break;\n   }\n\n   bool yChanged = true;\n   switch (scrollTargetVpos) {\n   case VPOS_TOP:\n      scrollY = scrollTargetY;\n      break;\n   case VPOS_CENTER:\n      scrollY =\n         scrollTargetY\n         - (viewportHeight - currHScrollbarThickness() - scrollTargetHeight)/2;\n      break;\n   case VPOS_BOTTOM:\n      scrollY =\n         scrollTargetY\n         - (viewportHeight - currHScrollbarThickness() - scrollTargetHeight);\n      break;\n   case VPOS_INTO_VIEW:\n      yChanged = calcScrollInto (scrollTargetY, scrollTargetHeight, &scrollY,\n                                 viewportHeight - currHScrollbarThickness());\n      break;\n   case VPOS_NO_CHANGE:\n      yChanged = false;\n      break;\n   }\n\n   if (xChanged || yChanged) {\n      adjustScrollPos ();\n      view->scrollTo (scrollX, scrollY);\n      if (drawAfterScrollReq) {\n         drawAfterScrollReq = false;\n         view->queueDrawTotal ();\n      }\n   }\n\n   scrollIdleId = -1;\n}\n\nvoid Layout::adjustScrollPos ()\n{\n   scrollX = misc::min (scrollX,\n      canvasWidth - (viewportWidth - vScrollbarThickness));\n   scrollX = misc::max (scrollX, 0);\n\n   scrollY = misc::min (scrollY,\n      canvasAscent + canvasDescent - (viewportHeight - hScrollbarThickness));\n   scrollY = misc::max (scrollY, 0);\n\n   _MSG(\"adjustScrollPos: scrollX=%d scrollY=%d\\n\", scrollX, scrollY);\n}\n\nbool Layout::calcScrollInto (int requestedValue, int requestedSize,\n                             int *value, int viewportSize)\n{\n   if (requestedSize > viewportSize) {\n      // The viewport size is smaller than the size of the region which will\n      // be shown. If the region is already visible, do not change the\n      // position. Otherwise, show the left/upper border, this is most likely\n      // what is needed.\n      if (*value >= requestedValue &&\n          *value + viewportSize < requestedValue + requestedSize)\n         return false;\n      else\n         requestedSize = viewportSize;\n   }\n\n   if (requestedValue < *value) {\n      *value = requestedValue;\n      return true;\n   } else if (requestedValue + requestedSize > *value + viewportSize) {\n      *value = requestedValue - viewportSize + requestedSize;\n      return true;\n   } else\n      return false;\n}\n\nvoid Layout::draw (View *view, Rectangle *area)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"draw\", \"%d, %d, %d * %d\",\n                  area->x, area->y, area->width, area->height);\n\n   Rectangle widgetArea, intersection, widgetDrawArea;\n\n   // First of all, draw background image. (Unlike background *color*,\n   // this is not a feature of the views.)\n   if (bgImage != NULL && bgImage->getImgbufSrc() != NULL)\n      style::drawBackgroundImage (view, bgImage, bgRepeat, bgAttachment,\n                                  bgPositionX, bgPositionY,\n                                  area->x, area->y, area->width,\n                                  area->height, 0, 0,\n                                  // Reference area: maximum of canvas size and\n                                  // viewport size.\n                                  misc::max (viewportWidth\n                                             - (canvasHeightGreater ?\n                                                vScrollbarThickness : 0),\n                                             canvasWidth),\n                                  misc::max (viewportHeight\n                                             - hScrollbarThickness,\n                                             canvasAscent + canvasDescent));\n\n   if (scrollIdleId != -1) {\n      /* scroll is pending, defer draw until after scrollIdle() */\n      drawAfterScrollReq = true;\n\n   } else if (topLevel) {\n      /* Draw the top level widget. */\n      widgetArea.x = topLevel->allocation.x;\n      widgetArea.y = topLevel->allocation.y;\n      widgetArea.width = topLevel->allocation.width;\n      widgetArea.height = topLevel->getHeight ();\n\n      if (area->intersectsWith (&widgetArea, &intersection)) {\n         view->startDrawing (&intersection);\n\n         /* Intersection in widget coordinates. */\n         widgetDrawArea.x = intersection.x - topLevel->allocation.x;\n         widgetDrawArea.y = intersection.y - topLevel->allocation.y;\n         widgetDrawArea.width = intersection.width;\n         widgetDrawArea.height = intersection.height;\n\n         DrawingContext context (&widgetArea);\n         topLevel->draw (view, &widgetDrawArea, &context);\n\n         view->finishDrawing (&intersection);\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nint Layout::currHScrollbarThickness()\n{\n   return (canvasWidth > viewportWidth) ? hScrollbarThickness : 0;\n}\n\nint Layout::currVScrollbarThickness()\n{\n   return (canvasAscent + canvasDescent > viewportHeight) ?\n          vScrollbarThickness : 0;\n}\n\n/**\n * Sets the anchor to scroll to.\n */\nvoid Layout::setAnchor (const char *anchor)\n{\n   _MSG(\"setAnchor (%s)\\n\", anchor);\n\n   if (requestedAnchor)\n      free (requestedAnchor);\n   requestedAnchor = anchor ? strdup (anchor) : NULL;\n   updateAnchor ();\n}\n\n/**\n * Used, when the widget is not allocated yet.\n */\nchar *Layout::addAnchor (Widget *widget, const char* name)\n{\n   return addAnchor (widget, name, -1);\n}\n\nchar *Layout::addAnchor (Widget *widget, const char* name, int y)\n{\n   String key (name);\n   if (anchorsTable->contains (&key))\n      return NULL;\n   else {\n      Anchor *anchor = new Anchor ();\n      anchor->name = strdup (name);\n      anchor->widget = widget;\n      anchor->y = y;\n\n      anchorsTable->put (new String (name), anchor);\n      updateAnchor ();\n\n      return anchor->name;\n   }\n}\n\nvoid Layout::changeAnchor (Widget *widget, char* name, int y)\n{\n   String key (name);\n   Anchor *anchor = anchorsTable->get (&key);\n   assert (anchor);\n   assert (anchor->widget == widget);\n   anchor->y = y;\n   updateAnchor ();\n}\n\nvoid Layout::removeAnchor (Widget *widget, char* name)\n{\n   String key (name);\n   anchorsTable->remove (&key);\n}\n\nvoid Layout::updateAnchor ()\n{\n   Anchor *anchor;\n   if (requestedAnchor) {\n      String key (requestedAnchor);\n      anchor = anchorsTable->get (&key);\n   } else\n      anchor = NULL;\n\n   if (anchor == NULL) {\n      /** \\todo Copy comment from old docs. */\n      if (scrollIdleId != -1 && !scrollIdleNotInterrupted) {\n         platform->removeIdle (scrollIdleId);\n         scrollIdleId = -1;\n      }\n   } else\n      if (anchor->y != -1)\n         scrollTo0 (HPOS_NO_CHANGE, VPOS_TOP, 0, anchor->y, 0, 0, false);\n}\n\nvoid Layout::setCursor (style::Cursor cursor)\n{\n   if (cursor != this->cursor) {\n      this->cursor = cursor;\n      view->setCursor (cursor);\n   }\n}\n\nvoid Layout::updateCursor ()\n{\n   if (widgetAtPoint && widgetAtPoint->style)\n      setCursor (widgetAtPoint->style->cursor);\n   else\n      setCursor (style::CURSOR_DEFAULT);\n}\n\nvoid Layout::setBgColor (style::Color *color)\n{\n   color->ref ();\n\n   if (bgColor)\n      bgColor->unref ();\n\n   bgColor = color;\n\n   if (view)\n      view->setBgColor (bgColor);\n}\n\nvoid Layout::setBgImage (style::StyleImage *bgImage,\n                         style::BackgroundRepeat bgRepeat,\n                         style::BackgroundAttachment bgAttachment,\n                         style::Length bgPositionX, style::Length bgPositionY)\n{\n   if (layoutImgRenderer && this->bgImage)\n      this->bgImage->removeExternalImgRenderer (layoutImgRenderer);\n\n   if (bgImage)\n      bgImage->ref ();\n\n   if (this->bgImage)\n      this->bgImage->unref ();\n\n   this->bgImage = bgImage;\n   this->bgRepeat = bgRepeat;\n   this->bgAttachment = bgAttachment;\n   this->bgPositionX = bgPositionX;\n   this->bgPositionY = bgPositionY;\n\n   if (bgImage) {\n      // Create instance of LayoutImgRenderer when needed. Until this\n      // layout is deleted, \"layoutImgRenderer\" will be kept, since it\n      // is not specific to the style, but only to this layout.\n      if (layoutImgRenderer == NULL)\n         layoutImgRenderer = new LayoutImgRenderer (this);\n      bgImage->putExternalImgRenderer (layoutImgRenderer);\n   }\n}\n\n\nvoid Layout::resizeIdle ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"resizeIdle\");\n\n   enterResizeIdle ();\n\n#ifdef DBG_RTFL\n   static int calls = 0;\n#endif\n\n   // There are two commits, 2863:b749629fbfc9 and 4645:ab70f9ce4353, the second\n   // reverting the former. Interrestingly, the second fixes a bug. However, it\n   // should still examined what happens here, and what happens the other calls\n   // to Layout::resizeIdle() which should be still in the queue. (See\n   // Layout::queueResize(), where resizeIdleId is indeed checked.)\n\n   while (resizeIdleId != -1) {\n      DBG_OBJ_MSGF (\"resize\", 1,\n                    \"Layout::resizeIdle calls = %d\\n\", ++calls);\n\n      for (typed::Iterator <Widget> it = queueResizeList->iterator();\n           it.hasNext (); ) {\n         Widget *widget = it.getNext ();\n\n         if (widget->resizeQueued ()) {\n            widget->setFlags (Widget::NEEDS_RESIZE);\n            widget->unsetFlags (Widget::RESIZE_QUEUED);\n         }\n\n         if (widget->allocateQueued ()) {\n            widget->setFlags (Widget::NEEDS_ALLOCATE);\n            widget->unsetFlags (Widget::ALLOCATE_QUEUED);\n         }\n\n         if (widget->extremesQueued ()) {\n            widget->setFlags (Widget::EXTREMES_CHANGED);\n            widget->unsetFlags (Widget::EXTREMES_QUEUED);\n         }\n      }\n      queueResizeList->clear ();\n\n      // Reset here, since below, queueResize() may be called again.\n      resizeIdleId = -1;\n\n      // If this method is triggered by a viewport change, we can save\n      // time when the toplevel widget is not affected (as for a toplevel\n      // image resource).\n      if (topLevel &&\n          (topLevel->needsResize () || topLevel->needsAllocate ())) {\n         Requisition requisition;\n         Allocation allocation;\n\n         topLevel->sizeRequest (&requisition);\n         DBG_OBJ_MSGF (\"resize\", 1, \"toplevel size: %d * (%d + %d)\",\n                       requisition.width, requisition.ascent,\n                       requisition.descent);\n\n         // This method is triggered by Widget::queueResize, which will,\n         // in any case, set NEEDS_ALLOCATE (indirectly, as ALLOCATE_QUEUED).\n         // This assertion helps to find inconsistencies. (Cases where\n         // this method is triggered by a viewport change, but the\n         // toplevel widget is not affected, are filtered out some lines\n         // above: \"if (topLevel && topLevel->needsResize ())\".)\n         assert (topLevel->needsAllocate ());\n\n         allocation.x = allocation.y = 0;\n         allocation.width = requisition.width;\n         allocation.ascent = requisition.ascent;\n         allocation.descent = requisition.descent;\n         topLevel->sizeAllocate (&allocation);\n\n         canvasWidth = requisition.width;\n         canvasAscent = requisition.ascent;\n         canvasDescent = requisition.descent;\n         emitter.emitCanvasSizeChanged (canvasWidth, \n                                        canvasAscent, canvasDescent);\n         // Tell the view about the new world size.\n         view->setCanvasSize (canvasWidth, canvasAscent, canvasDescent);\n\n         if (usesViewport) {\n            int currHThickness = currHScrollbarThickness();\n            int currVThickness = currVScrollbarThickness();\n\n            if (!canvasHeightGreater &&\n                canvasAscent + canvasDescent > viewportHeight - currHThickness) {\n               canvasHeightGreater = true;\n               DBG_OBJ_SET_SYM (\"canvasHeightGreater\",\n                                canvasHeightGreater ? \"true\" : \"false\");\n               containerSizeChanged ();\n            }\n\n            // Set viewport sizes.\n            view->setViewportSize (viewportWidth, viewportHeight,\n                                   currHThickness, currVThickness);\n         }\n\n         // views are redrawn via Widget::resizeDrawImpl ()\n      }\n   }\n   updateAnchor ();\n\n   DBG_OBJ_MSGF (\"resize\", 1,\n                 \"after resizeIdle: resizeIdleId = %d\", resizeIdleId);\n   DBG_OBJ_LEAVE ();\n\n   leaveResizeIdle ();\n}\n\nvoid Layout::queueDraw (int x, int y, int width, int height)\n{\n   Rectangle area;\n   area.x = x;\n   area.y = y;\n   area.width = width;\n   area.height = height;\n\n   if (area.isEmpty ()) return;\n\n   view->queueDraw (&area);\n}\n\nvoid Layout::queueDrawExcept (int x, int y, int width, int height,\n   int ex, int ey, int ewidth, int eheight) {\n\n   if (x == ex && y == ey && width == ewidth && height == eheight)\n      return;\n\n   // queueDraw() the four rectangles within rectangle (x, y, width, height)\n   // around rectangle (ex, ey, ewidth, eheight).\n   // Some or all of these may be empty.\n\n   // upper left corner of the intersection rectangle\n   int ix1 = misc::max (x, ex);\n   int iy1 = misc::max (y, ey);\n   // lower right corner of the intersection rectangle\n   int ix2 = misc::min (x + width, ex + ewidth);\n   int iy2 = misc::min (y + height, ey + eheight);\n\n   queueDraw (x, y, width, iy1 - y);\n   queueDraw (x, iy2, width, y + height - iy2);\n   queueDraw (x, iy1, ix1 - x, iy2 - iy1);\n   queueDraw (ix2, iy1, x + width - ix2, iy2 - iy1);\n}\n\nvoid Layout::queueResize (bool extremesChanged)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"queueResize\", \"%s\",\n                  extremesChanged ? \"true\" : \"false\");\n\n   if (resizeIdleId == -1) {\n      view->cancelQueueDraw ();\n\n      resizeIdleId = platform->addIdle (&Layout::resizeIdle);\n      DBG_OBJ_MSGF (\"resize\", 1, \"setting resizeIdleId = %d\", resizeIdleId);\n   }\n\n   emitter.emitResizeQueued (extremesChanged);\n\n   DBG_OBJ_LEAVE ();\n}\n\n\n// Views\n\nbool Layout::buttonEvent (ButtonEventType type, View *view, int numPressed,\n                          int x, int y, ButtonState state, int button)\n\n{\n   EventButton event;\n\n   moveToWidgetAtPoint (x, y, state);\n\n   event.xCanvas = x;\n   event.yCanvas = y;\n   event.state = state;\n   event.button = button;\n   event.numPressed = numPressed;\n\n   return processMouseEvent (&event, type);\n}\n\n/**\n * \\brief This function is called by a view, to delegate a motion notify\n * event.\n *\n * Arguments are similar to dw::core::Layout::buttonPress.\n */\nbool Layout::motionNotify (View *view, int x, int y, ButtonState state)\n{\n   EventButton event;\n\n   moveToWidgetAtPoint (x, y, state);\n\n   event.xCanvas = x;\n   event.yCanvas = y;\n   event.state = state;\n\n   return processMouseEvent (&event, MOTION_NOTIFY);\n}\n\n/**\n * \\brief This function is called by a view, to delegate a enter notify event.\n *\n * Arguments are similar to dw::core::Layout::buttonPress.\n */\nvoid Layout::enterNotify (View *view, int x, int y, ButtonState state)\n{\n   Widget *lastWidget;\n   EventCrossing event;\n\n   lastWidget = widgetAtPoint;\n   moveToWidgetAtPoint (x, y, state);\n\n   if (widgetAtPoint) {\n      event.state = state;\n      event.lastWidget = lastWidget;\n      event.currentWidget = widgetAtPoint;\n      widgetAtPoint->enterNotify (&event);\n   }\n}\n\n/**\n * \\brief This function is called by a view, to delegate a leave notify event.\n *\n * Arguments are similar to dw::core::Layout::buttonPress.\n */\nvoid Layout::leaveNotify (View *view, ButtonState state)\n{\n#if 0\n   Widget *lastWidget;\n   EventCrossing event;\n\n   lastWidget = widgetAtPoint;\n   moveOutOfView (state);\n\n   if (lastWidget) {\n      event.state = state;\n      event.lastWidget = lastWidget;\n      event.currentWidget = widgetAtPoint;\n      lastWidget->leaveNotify (&event);\n   }\n#else\n   moveOutOfView (state);\n#endif\n}\n\n/*\n * Return the widget at position (x, y). Return NULL, if there is no widget.\n */\nWidget *Layout::getWidgetAtPoint (int x, int y)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"getWidgetAtPoint\", \"%d, %d\", x, y);\n   Widget *widget;\n\n   if (topLevel && topLevel->wasAllocated ()) {\n      GettingWidgetAtPointContext context;\n      widget = topLevel->getWidgetAtPoint (x, y, &context);\n   } else\n      widget = NULL;\n\n   DBG_OBJ_MSGF (\"events\", 0, \"=> %p\", widget);\n   DBG_OBJ_LEAVE ();\n   return widget;\n}\n\n\n/*\n * Emit the necessary crossing events, when the mouse pointer has moved to\n * the given widget (by mouse or scrolling).\n */\nvoid Layout::moveToWidget (Widget *newWidgetAtPoint, ButtonState state)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"moveToWidget\", \"%p, %d\",\n                  newWidgetAtPoint, state);\n\n   Widget *ancestor, *w;\n   Widget **track;\n   int trackLen, i, i_a;\n   EventCrossing crossingEvent;\n\n   DBG_OBJ_MSGF (\"events\", 1, \"(old) widgetAtPoint = %p\", widgetAtPoint);\n\n   if (newWidgetAtPoint != widgetAtPoint) {\n      // The mouse pointer has been moved into another widget.\n      if (newWidgetAtPoint && widgetAtPoint)\n         ancestor =\n            newWidgetAtPoint->getNearestCommonAncestor (widgetAtPoint);\n      else if (newWidgetAtPoint)\n         ancestor = newWidgetAtPoint->getTopLevel ();\n      else\n         ancestor = widgetAtPoint->getTopLevel ();\n\n      // Construct the track.\n      trackLen = 0;\n      if (widgetAtPoint)\n         // first part\n         for (w = widgetAtPoint; w != ancestor; w = w->getParent ())\n            trackLen++;\n      trackLen++; // for the ancestor\n      if (newWidgetAtPoint)\n         // second part\n         for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())\n            trackLen++;\n\n      track = new Widget* [trackLen];\n      i = 0;\n      if (widgetAtPoint)\n         /* first part */\n         for (w = widgetAtPoint; w != ancestor; w = w->getParent ())\n            track[i++] = w;\n      i_a = i;\n      track[i++] = ancestor;\n      if (newWidgetAtPoint) {\n         /* second part */\n         i = trackLen - 1;\n         for (w = newWidgetAtPoint; w != ancestor; w = w->getParent ())\n            track[i--] = w;\n      }\n#if 0\n      MSG(\"Track: %s[ \", widgetAtPoint ? \"\" : \"nil \");\n      for (i = 0; i < trackLen; i++)\n         MSG(\"%s%p \", i == i_a ? \">\" : \"\", track[i]);\n      MSG(\"] %s\\n\", newWidgetAtPoint ? \"\" : \"nil\");\n#endif\n\n      /* Send events to the widgets on the track */\n      for (i = 0; i < trackLen; i++) {\n         crossingEvent.state = state;\n         crossingEvent.currentWidget = widgetAtPoint; // ???\n         crossingEvent.lastWidget = widgetAtPoint; // ???\n         if (i < i_a) {\n            track[i]->leaveNotify (&crossingEvent);\n         } else if (i == i_a) { /* ancestor */\n            /* Don't touch ancestor unless:\n             *   - moving into/from NULL,\n             *   - ancestor becomes the newWidgetAtPoint */\n            if (i_a == trackLen-1 && !newWidgetAtPoint)\n               track[i]->leaveNotify (&crossingEvent);\n            else if ((i_a == 0 && !widgetAtPoint) ||\n                     (i_a == trackLen-1 && newWidgetAtPoint))\n               track[i]->enterNotify (&crossingEvent);\n         } else {\n            track[i]->enterNotify (&crossingEvent);\n         }\n      }\n\n      delete[] track;\n\n      widgetAtPoint = newWidgetAtPoint;\n      updateCursor ();\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * \\brief Common processing of press, release and motion events.\n *\n * This function depends on that move_to_widget_at_point()\n * has been called before.\n */\nbool Layout::processMouseEvent (MousePositionEvent *event,\n                                ButtonEventType type)\n{\n   Widget *widget;\n\n   /*\n    * If the event is outside of the visible region of the canvas, treat it\n    * as occurring at the region's edge. Notably, this helps when selecting\n    * text.\n    */\n   if (event->xCanvas < scrollX)\n      event->xCanvas = scrollX;\n   else {\n      int maxX = scrollX + viewportWidth - currVScrollbarThickness() - 1;\n\n      if (event->xCanvas > maxX)\n         event->xCanvas = maxX;\n   }\n   if (event->yCanvas < scrollY)\n      event->yCanvas = scrollY;\n   else {\n      int maxY = misc::min(scrollY + viewportHeight -currHScrollbarThickness(),\n                           canvasAscent + canvasDescent) - 1;\n\n      if (event->yCanvas > maxY)\n         event->yCanvas = maxY;\n   }\n\n   widget = getWidgetAtPoint(event->xCanvas, event->yCanvas);\n\n   for (; widget; widget = widget->getParent ()) {\n      if (widget->isButtonSensitive ()) {\n         event->xWidget = event->xCanvas - widget->getAllocation()->x;\n         event->yWidget = event->yCanvas - widget->getAllocation()->y;\n\n         switch (type) {\n         case BUTTON_PRESS:\n            return widget->buttonPress ((EventButton*)event);\n\n         case BUTTON_RELEASE:\n            return widget->buttonRelease ((EventButton*)event);\n\n         case MOTION_NOTIFY:\n            return widget->motionNotify ((EventMotion*)event);\n\n         default:\n            misc::assertNotReached ();\n         }\n      }\n   }\n   if (type == BUTTON_PRESS)\n      return emitLinkPress (NULL, -1, -1, -1, -1, (EventButton*)event);\n   else if (type == BUTTON_RELEASE)\n      return emitLinkRelease(NULL, -1, -1, -1, -1, (EventButton*)event);\n\n   return false;\n}\n\n/*\n * This function must be called by a view, when the user has manually changed\n * the viewport position. It is *not* called, when the layout has requested the\n * position change.\n */\nvoid Layout::scrollPosChanged (View *view, int x, int y)\n{\n   if (x != scrollX || y != scrollY) {\n      scrollX = x;\n      scrollY = y;\n\n      setAnchor (NULL);\n      updateAnchor ();\n   }\n}\n\n/*\n * This function must be called by a viewport view, when its viewport size has\n * changed. It is *not* called, when the layout has requested the size change.\n */\nvoid Layout::viewportSizeChanged (View *view, int width, int height)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"viewportSizeChanged\", \"%p, %d, %d\",\n                 view, width, height);\n\n   /* If size changes, redraw this view. */\n   if (viewportWidth != width || viewportHeight != height) {\n      canvasHeightGreater = false;   // reset value here\n      viewportWidth = width;\n      viewportHeight = height;\n      containerSizeChanged ();\n\n      DBG_OBJ_SET_SYM (\"canvasHeightGreater\",\n                       canvasHeightGreater ? \"true\" : \"false\");\n      DBG_OBJ_SET_NUM (\"viewportWidth\", viewportWidth);\n      DBG_OBJ_SET_NUM (\"viewportHeight\", viewportHeight);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Layout::containerSizeChanged ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChanged\");\n\n   if (topLevel) {\n      topLevel->containerSizeChanged ();\n      queueResize (true);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/layout.hh",
    "content": "#ifndef __DW_LAYOUT_HH__\n#define __DW_LAYOUT_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief The central class for managing and drawing a widget tree.\n *\n * \\sa\\ref dw-overview, \\ref dw-layout-widgets, \\ref dw-layout-views\n */\nclass Layout: public lout::object::Object\n{\n   friend class Widget;\n\nprivate:\n   class LayoutImgRenderer: public style::StyleImage::ExternalImgRenderer\n   {\n      Layout *layout;\n\n   public:\n      LayoutImgRenderer (Layout *layout) { this->layout = layout; }\n\n      bool readyToDraw ();\n      void getBgArea (int *x, int *y, int *width, int *height);\n      void getRefArea (int *xRef, int *yRef, int *widthRef, int *heightRef);\n      style::StyleImage *getBackgroundImage ();\n      style::BackgroundRepeat getBackgroundRepeat ();\n      style::BackgroundAttachment getBackgroundAttachment ();\n      style::Length getBackgroundPositionX ();\n      style::Length getBackgroundPositionY ();\n      void draw (int x, int y, int width, int height);\n   };\n\n   LayoutImgRenderer *layoutImgRenderer;\n\npublic:\n   /**\n    * \\brief Receiver interface different signals.\n    *\n    * May be extended.\n    */\n   class Receiver: public lout::signal::Receiver\n   {\n   public:\n      virtual void resizeQueued (bool extremesChanged);\n      virtual void canvasSizeChanged (int width, int ascent, int descent);\n   };\n\n   class LinkReceiver: public lout::signal::Receiver\n   {\n   public:\n      /**\n       * \\brief Called, when a link is entered, left, or the position has\n       *    changed.\n       *\n       * When a link is entered, this method is called with the respective\n       * arguments. When a link is left, this method is called with all\n       * three arguments (\\em link, \\em x, \\em y) set to -1.\n       *\n       * When coordinates are supported, a change of the coordinates also\n       * causes emitting this signal.\n       */\n      virtual bool enter (Widget *widget, int link, int img, int x, int y);\n\n      /**\n       * \\brief Called, when the user has pressed the mouse button on a\n       *    link (but not yet released).\n       *\n       * The causing event is passed as \\em event.\n       */\n      virtual bool press (Widget *widget, int link, int img, int x, int y,\n                          EventButton *event);\n\n      /**\n       * \\brief Called, when the user has released the mouse button on a\n       *    link.\n       *\n       * The causing event is passed as \\em event.\n       */\n      virtual bool release (Widget *widget, int link, int img, int x, int y,\n                            EventButton *event);\n\n      /**\n       * \\brief Called, when the user has clicked on a link.\n       *\n       * For mouse interaction, this is equivalent to \"press\" and \"release\"\n       * on the same link. In this case, \\em event contains the \"release\"\n       * event.\n       *\n       *\n       * When activating links via keyboard is supported, only a \"clicked\"\n       * signal will be emitted, and \\em event will be NULL.\n       */\n      virtual bool click (Widget *widget, int link, int img, int x, int y,\n                          EventButton *event);\n   };\n\n   class LinkEmitter: public lout::signal::Emitter\n   {\n   private:\n      enum { ENTER, PRESS, RELEASE, CLICK };\n\n   protected:\n      bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,\n                           int argc, lout::object::Object **argv);\n\n   public:\n      inline void connectLink (LinkReceiver *receiver) { connect (receiver); }\n\n      bool emitEnter (Widget *widget, int link, int img, int x, int y);\n      bool emitPress (Widget *widget, int link, int img, int x, int y,\n                      EventButton *event);\n      bool emitRelease (Widget *widget, int link, int img, int x, int y,\n                        EventButton *event);\n      bool emitClick (Widget *widget, int link, int img, int x, int y,\n                      EventButton *event);\n   };\n\n   LinkEmitter linkEmitter;\n\nprivate:\n   class Emitter: public lout::signal::Emitter\n   {\n   private:\n      enum { RESIZE_QUEUED, CANVAS_SIZE_CHANGED };\n\n   protected:\n      bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,\n                           int argc, lout::object::Object **argv);\n\n   public:\n      inline void connectLayout (Receiver *receiver) { connect (receiver); }\n\n      void emitResizeQueued (bool extremesChanged);\n      void emitCanvasSizeChanged (int width, int ascent, int descent);\n   };\n\n   Emitter emitter;\n\n   class Anchor: public lout::object::Object\n   {\n   public:\n      char *name;\n      Widget *widget;\n      int y;\n\n      ~Anchor ();\n   };\n\n   Platform *platform;\n   View *view;\n   Widget *topLevel, *widgetAtPoint;\n   lout::container::typed::Vector<Widget> *queueResizeList;\n\n   /* The state, which must be projected into the view. */\n   style::Color *bgColor;\n   style::StyleImage *bgImage;\n   style::BackgroundRepeat bgRepeat;\n   style::BackgroundAttachment bgAttachment;\n   style::Length bgPositionX, bgPositionY;\n\n   style::Cursor cursor;\n   int canvasWidth, canvasAscent, canvasDescent;\n\n   bool usesViewport, drawAfterScrollReq;\n   int scrollX, scrollY, viewportWidth, viewportHeight;\n   bool canvasHeightGreater;\n   int hScrollbarThickness, vScrollbarThickness;\n\n   HPosition scrollTargetHpos;\n   VPosition scrollTargetVpos;\n   int scrollTargetX, scrollTargetY, scrollTargetWidth, scrollTargetHeight;\n\n   char *requestedAnchor;\n   int scrollIdleId, resizeIdleId;\n   bool scrollIdleNotInterrupted;\n\n   /* Anchors of the widget tree */\n   lout::container::typed::HashTable <lout::object::String, Anchor>\n                                     *anchorsTable;\n\n   SelectionState selectionState;\n   FindtextState findtextState;\n\n   enum ButtonEventType { BUTTON_PRESS, BUTTON_RELEASE, MOTION_NOTIFY };\n\n   void detachWidget (Widget *widget);\n\n   Widget *getWidgetAtPoint (int x, int y);\n   void moveToWidget (Widget *newWidgetAtPoint, ButtonState state);\n\n   /**\n    * \\brief Emit the necessary crossing events, when the mouse pointer has\n    *    moved to position (\\em x, \\em );\n    */\n   void moveToWidgetAtPoint (int x, int y, ButtonState state)\n   { moveToWidget (getWidgetAtPoint (x, y), state); }\n\n   /**\n    * \\brief Emit the necessary crossing events, when the mouse pointer\n    * has moved out of the view.\n    */\n   void moveOutOfView (ButtonState state) { moveToWidget (NULL, state); }\n\n   bool processMouseEvent (MousePositionEvent *event, ButtonEventType type);\n   bool buttonEvent (ButtonEventType type, View *view,\n                     int numPressed, int x, int y, ButtonState state,\n                     int button);\n   void resizeIdle ();\n   void setSizeHints ();\n   void draw (View *view, Rectangle *area);\n\n   void scrollTo0(HPosition hpos, VPosition vpos,\n                  int x, int y, int width, int height,\n                  bool scrollingInterrupted);\n   void scrollIdle ();\n   void adjustScrollPos ();\n   static bool calcScrollInto (int targetValue, int requestedSize,\n                               int *value, int viewportSize);\n   int currHScrollbarThickness();\n   int currVScrollbarThickness();\n\n   void updateAnchor ();\n\n   /* Widget */\n\n   char *addAnchor (Widget *widget, const char* name);\n   char *addAnchor (Widget *widget, const char* name, int y);\n   void changeAnchor (Widget *widget, char* name, int y);\n   void removeAnchor (Widget *widget, char* name);\n   void setCursor (style::Cursor cursor);\n   void updateCursor ();\n   void queueDraw (int x, int y, int width, int height);\n   void queueDrawExcept (int x, int y, int width, int height,\n      int ex, int ey, int ewidth, int eheight);\n   void queueResize (bool extremesChanged);\n   void removeWidget ();\n\n   /* For tests regarding the respective Layout and (mostly) Widget\n      methods. Accessed by respective methods (enter..., leave...,\n      ...Entered) defined here and in Widget. */\n\n   int resizeIdleCounter, queueResizeCounter, sizeAllocateCounter,\n      sizeRequestCounter, getExtremesCounter;\n\n   void enterResizeIdle () { resizeIdleCounter++; }\n   void leaveResizeIdle () { resizeIdleCounter--; }\n\npublic:\n   Layout (Platform *platform);\n   ~Layout ();\n\n   inline void connectLink (LinkReceiver *receiver)\n   { linkEmitter.connectLink (receiver); }\n\n   inline bool emitLinkEnter (Widget *w, int link, int img, int x, int y)\n   { return linkEmitter.emitEnter (w, link, img, x, y); }\n\n   inline bool emitLinkPress (Widget *w, int link, int img,\n                              int x, int y, EventButton *event)\n   { return linkEmitter.emitPress (w, link, img, x, y, event); }\n\n   inline bool emitLinkRelease (Widget *w, int link, int img,\n                                int x, int y, EventButton *event)\n   { return linkEmitter.emitRelease (w, link, img, x, y, event); }\n\n   inline bool emitLinkClick (Widget *w, int link, int img,\n                              int x, int y, EventButton *event)\n   { return linkEmitter.emitClick (w, link, img, x, y, event); }\n\n   lout::misc::ZoneAllocator *textZone;\n\n   void addWidget (Widget *widget);\n   void setWidget (Widget *widget);\n\n   void attachView (View *view);\n   void detachView (View *view);\n\n   inline bool getUsesViewport () { return usesViewport; }\n   inline int getWidthViewport () { return viewportWidth; }\n   inline int getHeightViewport ()  { return viewportHeight; }\n   inline int getScrollPosX ()  { return scrollX; }\n   inline int getScrollPosY ()  { return scrollY; }\n\n   /* public */\n\n   void scrollTo (HPosition hpos, VPosition vpos,\n                  int x, int y, int width, int height);\n   void scroll (ScrollCommand);\n   void setAnchor (const char *anchor);\n\n   /* View */\n\n   inline void expose (View *view, Rectangle *area) {\n      DBG_OBJ_ENTER (\"draw\", 0, \"expose\", \"%d, %d, %d * %d\",\n                     area->x, area->y, area->width, area->height);\n      draw (view, area);\n      DBG_OBJ_LEAVE ();\n   }\n\n   /**\n    * \\brief This function is called by a view, to delegate a button press\n    * event.\n    *\n    * \\em numPressed is 1 for simple presses, 2 for double presses etc. (more\n    * that 2 is never needed), \\em x and \\em y the world coordinates, and\n    * \\em button the number of the button pressed.\n    */\n   inline bool buttonPress (View *view, int numPressed, int x, int y,\n                            ButtonState state, int button)\n   {\n      return buttonEvent (BUTTON_PRESS, view, numPressed, x, y, state, button);\n   }\n\n   void containerSizeChanged ();\n\n   /**\n    * \\brief This function is called by a view, to delegate a button press\n    * event.\n    *\n    * Arguments are similar to dw::core::Layout::buttonPress.\n    */\n   inline bool buttonRelease (View *view, int numPressed, int x, int y,\n                              ButtonState state, int button)\n   {\n      return buttonEvent (BUTTON_RELEASE, view, numPressed, x, y, state,\n                          button);\n   }\n\n   bool motionNotify (View *view, int x, int y, ButtonState state);\n   void enterNotify (View *view, int x, int y, ButtonState state);\n   void leaveNotify (View *view, ButtonState state);\n\n   void scrollPosChanged (View *view, int x, int y);\n   void viewportSizeChanged (View *view, int width, int height);\n\n   inline Platform *getPlatform ()\n   {\n      return platform;\n   }\n\n   /* delegated */\n\n   inline int textWidth (style::Font *font, const char *text, int len)\n   {\n      return platform->textWidth (font, text, len);\n   }\n\n   inline char *textToUpper (const char *text, int len)\n   {\n      return platform->textToUpper (text, len);\n   }\n\n   inline char *textToLower (const char *text, int len)\n   {\n      return platform->textToLower (text, len);\n   }\n\n   inline int nextGlyph (const char *text, int idx)\n   {\n      return platform->nextGlyph (text, idx);\n   }\n\n   inline int prevGlyph (const char *text, int idx)\n   {\n      return platform->prevGlyph (text, idx);\n   }\n\n   inline float dpiX ()\n   {\n      return platform->dpiX ();\n   }\n\n   inline float dpiY ()\n   {\n      return platform->dpiY ();\n   }\n\n   inline style::Font *createFont (style::FontAttrs *attrs, bool tryEverything)\n   {\n      return platform->createFont (attrs, tryEverything);\n   }\n\n   inline bool fontExists (const char *name)\n   {\n      return platform->fontExists (name);\n   }\n\n   inline style::Color *createColor (int color)\n   {\n      return platform->createColor (color);\n   }\n\n   inline style::Tooltip *createTooltip (const char *text)\n   {\n      return platform->createTooltip (text);\n   }\n\n   inline void cancelTooltip ()\n   {\n      return platform->cancelTooltip ();\n   }\n\n   inline Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height,\n                                double gamma)\n   {\n      return platform->createImgbuf (type, width, height, gamma);\n   }\n\n   inline void copySelection(const char *text)\n   {\n      platform->copySelection(text);\n   }\n\n   inline ui::ResourceFactory *getResourceFactory ()\n   {\n      return platform->getResourceFactory ();\n   }\n\n   inline void connect (Receiver *receiver) {\n      emitter.connectLayout (receiver); }\n\n   /** \\brief See dw::core::FindtextState::search. */\n   inline FindtextState::Result search (const char *str, bool caseSens,\n                                        int backwards)\n      { return findtextState.search (str, caseSens, backwards); }\n\n   /** \\brief See dw::core::FindtextState::resetSearch. */\n   inline void resetSearch () { findtextState.resetSearch (); }\n\n   void setBgColor (style::Color *color);\n   void setBgImage (style::StyleImage *bgImage,\n                    style::BackgroundRepeat bgRepeat,\n                    style::BackgroundAttachment bgAttachment,\n                    style::Length bgPositionX, style::Length bgPositionY);\n\n   inline style::Color* getBgColor () { return bgColor; }\n   inline style::StyleImage* getBgImage () { return bgImage; }\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_LAYOUT_HH__\n\n"
  },
  {
    "path": "dw/listitem.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"listitem.hh\"\n#include \"../lout/debug.hh\"\n#include <stdio.h>\n\nnamespace dw {\n\nint ListItem::CLASS_ID = -1;\n\nListItem::ListItem (ListItem *ref, bool limitTextWidth):\n   AlignedTextblock (limitTextWidth)\n{\n   DBG_OBJ_CREATE (\"dw::ListItem\");\n   registerName (\"dw::ListItem\", &CLASS_ID);\n   setRefTextblock (ref);\n}\n\nListItem::~ListItem()\n{\n   DBG_OBJ_DELETE ();\n}\n\nbool ListItem::usesMaxGeneratorWidth ()\n{\n   return true;\n}\n\nvoid ListItem::initWithWidget (core::Widget *widget,\n                                core::style::Style *style)\n{\n   hasListitemValue = true;\n   addWidget (widget, style);\n   addSpace (style);\n   if (style->listStylePosition == core::style::LIST_STYLE_POSITION_OUTSIDE)\n      updateValue ();\n}\n\nvoid ListItem::initWithText (const char *text, core::style::Style *style)\n{\n   hasListitemValue = true;\n   addText (text, style);\n   addSpace (style);\n   if (style->listStylePosition == core::style::LIST_STYLE_POSITION_OUTSIDE)\n      updateValue ();\n}\n\nint ListItem::getValue ()\n{\n   if (words->size () == 0)\n      return 0;\n   else\n      return words->getRef(0)->size.width + words->getRef(0)->origSpace;\n}\n\nvoid ListItem::setMaxValue (int maxValue, int value)\n{\n   leftInnerPadding = maxValue;\n   line1Offset = - value;\n   redrawY = 0;\n   queueResize (0, true);\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/listitem.hh",
    "content": "#ifndef __DW_LISTITEM_HH__\n#define __DW_LISTITEM_HH__\n\n#include \"core.hh\"\n#include \"alignedtextblock.hh\"\n\nnamespace dw {\n\nclass ListItem: public AlignedTextblock\n{\nprotected:\n   int getValue ();\n   void setMaxValue (int maxValue, int value);\n\npublic:\n   static int CLASS_ID;\n\n   ListItem(ListItem *ref, bool limitTextWidth);\n   ~ListItem();\n\n   bool usesMaxGeneratorWidth ();\n\n   void initWithWidget (core::Widget *widget, core::style::Style *style);\n   void initWithText (const char *text, core::style::Style *style);\n};\n\n} // namespace dw\n\n#endif // __DW_LISTITEM_HH__\n"
  },
  {
    "path": "dw/oofawarewidget.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"oofawarewidget.hh\"\n#include \"ooffloatsmgr.hh\"\n#include \"oofposabsmgr.hh\"\n#include \"oofposrelmgr.hh\"\n#include \"oofposfixedmgr.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace lout::object;\nusing namespace lout::misc;\nusing namespace lout::container::typed;\n\nnamespace dw {\n\nnamespace oof {\n\nconst char *OOFAwareWidget::OOFM_NAME[NUM_OOFM] = {\n   \"FLOATS\", \"ABSOLUTE\", \"RELATIVE\", \"FIXED\"\n};\n\nint OOFAwareWidget::CLASS_ID = -1;\n\nOOFAwareWidget::OOFAwareWidget ()\n{\n   DBG_OBJ_CREATE (\"dw::oof::OOFAwareWidget\");\n   registerName (\"dw::oof::OOFAwareWidget\", &CLASS_ID);\n\n   for (int i = 0; i < NUM_OOFM; i++) {\n      oofContainer[i] = NULL;\n      DBG_OBJ_ARRSET_PTR (\"oofContainer\", i, oofContainer[i]);\n      outOfFlowMgr[i] = NULL;\n   }\n}\n\nOOFAwareWidget::~OOFAwareWidget ()\n{\n   for (int i = 0; i < NUM_OOFM; i++) {\n      if(outOfFlowMgr[i]) {\n         // I feel more comfortable by letting the OOF aware widget delete \n         // these widgets, instead of doing this in ~OutOfFlowMgr.\n         for (int j = 0; j < outOfFlowMgr[i]->getNumWidgets (); j++)\n            delete outOfFlowMgr[i]->getWidget (j);\n         \n         delete outOfFlowMgr[i];\n      }\n   }\n\n   DBG_OBJ_DELETE ();\n}\n\nconst char *OOFAwareWidget::stackingLevelText (int level)\n{\n   switch (level) {\n   case SL_START:      return \"START\";\n   case SL_BACKGROUND: return \"BACKGROUND\";\n   case SL_SC_BOTTOM:  return \"SC_BOTTOM\";\n   case SL_IN_FLOW:    return \"IN_FLOW\";\n   case SL_OOF_REF:    return \"OOF_REF\";\n   case SL_OOF_CONT:   return \"OOF_CONT\";\n   case SL_SC_TOP:     return \"SC_TOP\";\n   case SL_END:        return \"END\";\n   default:            return \"???\";\n   }\n}\n\nvoid OOFAwareWidget::notifySetAsTopLevel ()\n{\n   for (int i = 0; i < NUM_OOFM; i++) {\n      oofContainer[i] = this;\n      DBG_OBJ_ARRSET_PTR (\"oofContainer\", i, oofContainer[i]);\n   }\n}\n\nint OOFAwareWidget::getOOFMIndex (Widget *widget)\n{\n   DBG_OBJ_ENTER_O (\"construct\", 0, NULL, \"getOOFMIndex\", \"%p\", widget);  \n   DBG_OBJ_MSGF_O (\"construct\", 1, NULL, \"position = %s, float = %s\",\n                   widget->getStyle()->position\n                   == style::POSITION_STATIC ? \"static\" :\n                   (widget->getStyle()->position\n                    == style::POSITION_RELATIVE ? \"relative\" :\n                    (widget->getStyle()->position\n                     == style::POSITION_ABSOLUTE ? \"absolute\" :\n                     (widget->getStyle()->position\n                      == style::POSITION_FIXED ? \"fixed\" : \"???\"))),\n                   widget->getStyle()->vloat == style::FLOAT_NONE ? \"none\" :\n                   (widget->getStyle()->vloat == style::FLOAT_LEFT ? \"left\" :\n                    (widget->getStyle()->vloat == style::FLOAT_RIGHT ?\n                     \"right\" : \"???\")));\n\n   int index = -1;\n   if (testWidgetFloat (widget))\n      index = OOFM_FLOATS;\n   else if (testWidgetAbsolutelyPositioned (widget))\n      index = OOFM_ABSOLUTE;\n   else if (testWidgetRelativelyPositioned (widget))\n      index = OOFM_RELATIVE;\n   else if (testWidgetFixedlyPositioned (widget))\n      index = OOFM_FIXED;\n   else\n      lout::misc::assertNotReached ();\n\n   DBG_OBJ_LEAVE_VAL_O (NULL, \"%d (%s)\", index, OOFM_NAME[index]);\n   return index;\n}\n\nbool OOFAwareWidget::isOOFContainer (Widget *widget, int oofmIndex)\n{\n   // TODO The methods isPossibleContainer() and isPossibleContainerParent()\n   // are only used in few cases. Does not matter currently, however.\n\n   switch (oofmIndex) {\n   case OOFM_FLOATS:\n      return widget->instanceOf (OOFAwareWidget::CLASS_ID) &&\n         (// For floats, only some OOF aware widgets are considered as\n          // containers.\n          ((OOFAwareWidget*)widget)->isPossibleOOFContainer (OOFM_FLOATS) &&\n          // The second condition: that this block is \"out of flow\", in a\n          // wider sense.\n          (// The toplevel widget is \"out of flow\", since there is no\n           // parent, and so no context.\n           widget->getParent() == NULL ||\n           // A similar reasoning applies to a widget with an\n           // unsuitable parent (typical example: a table cell (this\n           // is also a text block, so possible float container)\n           // within a table widget, which is not a suitable float\n           // container parent).\n           !(widget->getParent()->instanceOf (OOFAwareWidget::CLASS_ID) &&\n             ((OOFAwareWidget*)widget->getParent())\n                 ->isPossibleOOFContainerParent (OOFM_FLOATS)) ||\n           // Inline blocks are containing blocks, too.\n           widget->getStyle()->display == DISPLAY_INLINE_BLOCK ||\n           // Same for blocks with 'overview' set to another value than\n           // (the default value) 'visible'.\n           widget->getStyle()->overflow != OVERFLOW_VISIBLE ||\n           // Finally, \"out of flow\" in a narrower sense: floats;\n           // absolutely and fixedly positioned elements. (No\n           // relatively positioned elements; since the latters\n           // constitute a stacking context, drawing of floats gets\n           // somewhat more complicated; see \"interrupting the drawing\n           // process\" in \"dw-stacking-context.doc\".\n           testWidgetOutOfFlow (widget)));\n      \n   case OOFM_RELATIVE:\n   case OOFM_ABSOLUTE:\n      return widget->instanceOf (OOFAwareWidget::CLASS_ID) &&\n         (widget->getParent() == NULL ||\n          OOFAwareWidget::testWidgetPositioned (widget));\n\n      \n   case OOFM_FIXED:\n      // The single container for fixedly positioned elements is the\n      // toplevel (canvas; actually the viewport). (The toplevel\n      // widget should always be a textblock; at least this is the\n      // case in dillo.)\n      return widget->getParent() == NULL;\n\n   default:\n      // compiler happiness\n      lout::misc::assertNotReached ();\n      return false;\n   }\n}\n\nvoid OOFAwareWidget::notifySetParent ()\n{\n   // Search for containing blocks.\n   for (int oofmIndex = 0; oofmIndex < NUM_OOFM; oofmIndex++) {\n      oofContainer[oofmIndex] = NULL;\n\n      for (Widget *widget = this;\n           widget != NULL && oofContainer[oofmIndex] == NULL;\n           widget = widget->getParent ())\n         if (isOOFContainer (widget, oofmIndex)) {\n            assert (widget->instanceOf (OOFAwareWidget::CLASS_ID));\n            oofContainer[oofmIndex] = (OOFAwareWidget*)widget;\n         }\n   \n      DBG_OBJ_ARRSET_PTR (\"oofContainer\", oofmIndex, oofContainer[oofmIndex]);\n\n      assert (oofContainer[oofmIndex] != NULL);\n   }\n}\n\nvoid OOFAwareWidget::initOutOfFlowMgrs ()\n{\n   if (oofContainer[OOFM_FLOATS]->outOfFlowMgr[OOFM_FLOATS] == NULL) {\n      oofContainer[OOFM_FLOATS]->outOfFlowMgr[OOFM_FLOATS] =\n         new OOFFloatsMgr (oofContainer[OOFM_FLOATS], OOFM_FLOATS);\n      DBG_OBJ_ASSOC (oofContainer[OOFM_FLOATS],\n                     oofContainer[OOFM_FLOATS]->outOfFlowMgr[OOFM_FLOATS]);\n   }\n\n   if (oofContainer[OOFM_ABSOLUTE]->outOfFlowMgr[OOFM_ABSOLUTE] == NULL) {\n      oofContainer[OOFM_ABSOLUTE]->outOfFlowMgr[OOFM_ABSOLUTE] =\n         new OOFPosAbsMgr (oofContainer[OOFM_ABSOLUTE]);\n      DBG_OBJ_ASSOC (oofContainer[OOFM_ABSOLUTE],\n                     oofContainer[OOFM_ABSOLUTE]->outOfFlowMgr[OOFM_ABSOLUTE]);\n   }\n\n   if (oofContainer[OOFM_RELATIVE]->outOfFlowMgr[OOFM_RELATIVE] == NULL) {\n      oofContainer[OOFM_RELATIVE]->outOfFlowMgr[OOFM_RELATIVE] =\n         new OOFPosRelMgr (oofContainer[OOFM_RELATIVE]);\n      DBG_OBJ_ASSOC (oofContainer[OOFM_RELATIVE],\n                     oofContainer[OOFM_RELATIVE]->outOfFlowMgr[OOFM_RELATIVE]);\n   }\n\n   if (oofContainer[OOFM_FIXED]->outOfFlowMgr[OOFM_FIXED] == NULL) {\n      oofContainer[OOFM_FIXED]->outOfFlowMgr[OOFM_FIXED] =\n         new OOFPosFixedMgr (oofContainer[OOFM_FIXED]);\n      DBG_OBJ_ASSOC (oofContainer[OOFM_FIXED],\n                     oofContainer[OOFM_FIXED]->outOfFlowMgr[OOFM_FIXED]);\n   }\n}\n\nvoid OOFAwareWidget::correctRequisitionByOOF (Requisition *requisition,\n                                              void (*splitHeightFun) (int, int*,\n                                                                      int*))\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"correctRequisitionByOOF\", \"%d * (%d + %d), ...\",\n                  requisition->width, requisition->ascent,\n                  requisition->descent);\n\n   requisitionWithoutOOF = *requisition;\n\n   for (int i = 0; i < NUM_OOFM; i++) {\n      if (outOfFlowMgr[i]) {\n         DBG_OBJ_MSGF (\"resize\", 1, \"OOFM for %s\", OOFM_NAME[i]);\n         DBG_OBJ_MSG_START ();\n\n         int oofWidth, oofHeight;\n\n         outOfFlowMgr[i]->getSize (requisition, &oofWidth, &oofHeight);\n         DBG_OBJ_MSGF (\"resize\", 1, \"result: %d * %d\", oofWidth, oofHeight);\n\n         if (oofWidth > requisition->width) {\n            if (outOfFlowMgr[i]->containerMustAdjustExtraSpace () &&\n                adjustExtraSpaceWhenCorrectingRequisitionByOOF ()) {\n               extraSpace.right = max (extraSpace.right,\n                                       oofWidth - requisition->width);\n               DBG_OBJ_SET_NUM (\"extraSpace.right\", extraSpace.right);\n            }\n\n            requisition->width = oofWidth;\n         }\n\n         if (oofHeight > requisition->ascent + requisition->descent) {\n            if (outOfFlowMgr[i]->containerMustAdjustExtraSpace () &&\n                adjustExtraSpaceWhenCorrectingRequisitionByOOF ()) {\n               extraSpace.bottom = max (extraSpace.bottom,\n                                        oofHeight - (requisition->ascent +\n                                                     requisition->descent));\n               DBG_OBJ_SET_NUM (\"extraSpace.bottom\", extraSpace.bottom);\n            }\n   \n            splitHeightFun (oofHeight,\n                            &requisition->ascent, &requisition->descent);\n         }\n\n         if (!adjustExtraSpaceWhenCorrectingRequisitionByOOF ()) {\n            requisitionWithoutOOF.width = max (requisitionWithoutOOF.width,\n                                               oofWidth);\n            if (oofHeight >\n                requisitionWithoutOOF.ascent + requisitionWithoutOOF.descent)\n               splitHeightFun (oofHeight, &requisitionWithoutOOF.ascent,\n                               &requisitionWithoutOOF.descent);\n         }\n\n         DBG_OBJ_MSGF (\"resize\", 1, \"after correction: %d * (%d + %d)\",\n                       requisition->width, requisition->ascent,\n                       requisition->descent);\n         DBG_OBJ_MSG_END ();\n      } else\n         DBG_OBJ_MSGF (\"resize\", 1, \"no OOFM for %s\", OOFM_NAME[i]);\n   }\n\n   DBG_OBJ_SET_NUM (\"requisitionWithoutOOF.width\", requisitionWithoutOOF.width);\n   DBG_OBJ_SET_NUM (\"requisitionWithoutOOF.ascent\",\n                    requisitionWithoutOOF.ascent);\n   DBG_OBJ_SET_NUM (\"requisitionWithoutOOF.descent\",\n                    requisitionWithoutOOF.descent);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFAwareWidget::correctExtremesByOOF (Extremes *extremes)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"correctExtremesByOOF\", \"%d (%d) / %d (%d)\",\n                  extremes->minWidth, extremes->minWidthIntrinsic,\n                  extremes->maxWidth, extremes->maxWidthIntrinsic);\n\n   for (int i = 0; i < NUM_OOFM; i++) {\n      if (outOfFlowMgr[i]) {\n         DBG_OBJ_MSGF (\"resize\", 1, \"OOFM for %s\", OOFM_NAME[i]);\n         DBG_OBJ_MSG_START ();\n\n         int oofMinWidth, oofMaxWidth;\n         outOfFlowMgr[i]->getExtremes (extremes, &oofMinWidth, &oofMaxWidth);\n         DBG_OBJ_MSGF (\"resize\", 1, \"result: %d / %d\",\n                       oofMinWidth, oofMaxWidth);\n\n         extremes->minWidth = max (extremes->minWidth, oofMinWidth);\n         extremes->minWidthIntrinsic = max (extremes->minWidthIntrinsic,\n                                            oofMinWidth);\n         extremes->maxWidth = max (extremes->maxWidth, oofMaxWidth);\n         extremes->maxWidthIntrinsic = max (extremes->maxWidthIntrinsic,\n                                            oofMinWidth);\n         extremes->adjustmentWidth = max (extremes->adjustmentWidth,\n                                          oofMinWidth);\n\n         DBG_OBJ_MSGF (\"resize\", 1, \"after correction: %d (%d) / %d (%d)\",\n                       extremes->minWidth, extremes->minWidthIntrinsic,\n                       extremes->maxWidth, extremes->maxWidthIntrinsic);\n         DBG_OBJ_MSG_END ();\n      } else\n         DBG_OBJ_MSGF (\"resize\", 1, \"no OOFM for %s\", OOFM_NAME[i]);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFAwareWidget::sizeAllocateStart (Allocation *allocation)\n{\n\n   for (int i = 0; i < NUM_OOFM; i++)\n      if (oofContainer[i]->outOfFlowMgr[i])\n         oofContainer[i]->outOfFlowMgr[i]->sizeAllocateStart (this, allocation);\n}\n\nvoid OOFAwareWidget::sizeAllocateEnd ()\n{\n   for (int i = 0; i < NUM_OOFM; i++)\n      if (oofContainer[i]->outOfFlowMgr[i])\n         oofContainer[i]->outOfFlowMgr[i]->sizeAllocateEnd (this);\n}\n\nvoid OOFAwareWidget::containerSizeChangedForChildrenOOF ()\n{\n   for (int i = 0; i < NUM_OOFM; i++)\n      if (outOfFlowMgr[i])\n         outOfFlowMgr[i]->containerSizeChangedForChildren ();\n}\n\nbool OOFAwareWidget::doesWidgetOOFInterruptDrawing (Widget *widget)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"doesWidgetOOFInterruptDrawing\", \"%p\", widget);\n\n   bool result;\n   if (IMPL_POS) {\n      // This is the generator of the widget.\n      int oofmIndex = getOOFMIndex (widget);\n      DBG_OBJ_MSGF (\"draw\", 1, \"oofmIndex = %d\", oofmIndex);\n      \n      int cl = oofContainer[oofmIndex]->stackingContextWidget->getLevel (),\n         gl = stackingContextWidget->getLevel ();\n      result = cl < gl;\n      \n      DBG_OBJ_MSGF (\"draw\", 1,\"%d < %d => %s\", cl, gl, boolToStr (result));\n   } else\n      result = false;\n   \n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr (result));\n   return result;\n}\n\nvoid OOFAwareWidget::draw (View *view, Rectangle *area, DrawingContext *context)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"draw\", \"%d, %d, %d * %d\",\n                  area->x, area->y, area->width, area->height);\n\n   for (int level = SL_START + 1; level < SL_END; level++)\n      drawLevel (view, area, level, context);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFAwareWidget::drawLevel (View *view, Rectangle *area, int level,\n                                DrawingContext *context)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"OOFAwareWidget::drawLevel\",\n                  \"(%d, %d, %d * %d), %s\",\n                  area->x, area->y, area->width, area->height,\n                  stackingLevelText (level));\n\n   switch (level) {\n   case SL_START:\n      break;\n\n   case SL_BACKGROUND:\n      drawWidgetBox (view, area, false);\n      break;\n\n   case SL_SC_BOTTOM:\n      if (stackingContextMgr)\n         stackingContextMgr->drawBottom (view, area, context);\n      break;\n\n   case SL_IN_FLOW:\n      // Should be implemented in the sub class.\n      break;\n\n   case SL_OOF_REF:\n      // Should be implemented in the sub class (when references are hold).\n      break;\n\n   case SL_OOF_CONT:\n      drawOOF (view, area, context);\n      break;\n\n   case SL_SC_TOP:\n      if (stackingContextMgr)\n         stackingContextMgr->drawTop (view, area, context);\n      break;\n\n   case SL_END:\n      break;\n\n   default:\n      fprintf (stderr, \"OOFAwareWidget::drawLevel: invalid level %s (%d)\",\n               stackingLevelText (level), level);\n      break;\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFAwareWidget::drawOOF (View *view, Rectangle *area,\n                              DrawingContext *context)\n{\n   for (int i = 0; i < NUM_OOFM; i++) {\n      if(outOfFlowMgr[i])\n         outOfFlowMgr[i]->draw (view, area, context);\n   }\n} \n\nWidget *OOFAwareWidget::getWidgetAtPoint (int x, int y,\n                                          GettingWidgetAtPointContext *context)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"getWidgetAtPoint\", \"%d, %d\", x, y);\n   Widget *widgetAtPoint = NULL;\n\n   if (inAllocation (x, y)) {\n      for (int level = SL_END - 1; widgetAtPoint == NULL && level > SL_START;\n           level--)\n         widgetAtPoint = getWidgetAtPointLevel (x, y, level, context);\n   }\n   \n   DBG_OBJ_MSGF (\"events\", 1, \"=> %p\", widgetAtPoint);\n   DBG_OBJ_LEAVE ();\n   return widgetAtPoint;\n}\n\nWidget *OOFAwareWidget::getWidgetAtPointLevel (int x, int y, int level,\n                                               GettingWidgetAtPointContext\n                                               *context)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"OOFAwareWidget::getWidgetAtPointLevel\",\n                  \"%d, %d, %s\", x, y, stackingLevelText (level));\n\n   Widget *widgetAtPoint = NULL;\n\n   switch (level) {\n   case SL_BACKGROUND:\n      if (inAllocation (x, y))\n         widgetAtPoint = this;\n      break;\n\n   case SL_SC_BOTTOM:\n      if (stackingContextMgr)\n         widgetAtPoint =\n            stackingContextMgr->getBottomWidgetAtPoint (x, y, context);\n      break;\n\n   case SL_IN_FLOW:\n      // Should be implemented in the sub class.\n      assertNotReached (\"getWidgetAtPoint (SL_IN_FLOW) for %s\",\n                        getClassName ());\n      break;\n\n   case SL_OOF_REF:\n      // Should be implemented in the sub class (when references are hold).\n      break;\n\n   case SL_OOF_CONT:\n      widgetAtPoint = getWidgetOOFAtPoint (x, y, context);\n      break;\n\n   case SL_SC_TOP:\n      if (stackingContextMgr)\n         widgetAtPoint = \n            stackingContextMgr->getTopWidgetAtPoint (x, y, context);\n      break;\n\n   default:\n      fprintf (stderr,\n               \"OOFAwareWidget::getWidgetAtPointLevel: invalid level %s (%d)\",\n               stackingLevelText (level), level);\n      break;\n   }\n\n   DBG_OBJ_MSGF (\"events\", 1, \"=> %p\", widgetAtPoint);\n   DBG_OBJ_LEAVE ();\n   return widgetAtPoint;\n}\n\nWidget *OOFAwareWidget::getWidgetOOFAtPoint (int x, int y,\n                                             GettingWidgetAtPointContext\n                                             *context)\n{\n   Widget *widgetAtPoint = NULL;\n\n   for (int i = NUM_OOFM -1; widgetAtPoint == NULL && i >= 0; i--) {\n      if(outOfFlowMgr[i])\n         widgetAtPoint = outOfFlowMgr[i]->getWidgetAtPoint (x, y, context);\n   }\n\n   return widgetAtPoint;\n}\n\nvoid OOFAwareWidget::removeChild (Widget *child)\n{\n   // Sub classes should implement this method (and Textblock and\n   // Table do so), so this point is only reached from\n   // ~OOFAwareWidget, which removes widgets out of flow.\n   assert (isWidgetOOF (child));\n}\n\nvoid OOFAwareWidget::updateReference (int ref)\n{\n   notImplemented (\"OOFAwareWidget::updateReference\");\n}\n\nvoid OOFAwareWidget::widgetRefSizeChanged (int externalIndex)\n{\n   notImplemented (\"OOFAwareWidget::widgetRefSizeChanged\");\n}\n\nvoid OOFAwareWidget::oofSizeChanged (bool extremesChanged)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"oofSizeChanged\", \"%s\",\n                  extremesChanged ? \"true\" : \"false\");\n   queueResize (-1, extremesChanged);\n\n   // Extremes changes may become also relevant for the children.\n   if (extremesChanged)\n      containerSizeChanged ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nint OOFAwareWidget::getGeneratorX (int oofmIndex)\n{\n   notImplemented (\"OOFAwareWidget::getGeneratorX\");\n   return 0;\n}\n\nint OOFAwareWidget::getGeneratorY (int oofmIndex)\n{\n   notImplemented (\"OOFAwareWidget::getGeneratorY\");\n   return 0;\n}\n\nint OOFAwareWidget::getGeneratorWidth ()\n{\n   notImplemented (\"OOFAwareWidget::getGeneratorWidth\");\n   return 0;\n}\n\nint OOFAwareWidget::getMaxGeneratorWidth ()\n{\n   notImplemented (\"OOFAwareWidget::getMaxGeneratorWidth\");\n   return 0;\n}\n\nbool OOFAwareWidget::usesMaxGeneratorWidth ()\n{\n   notImplemented (\"OOFAwareWidget::usesMaxGeneratorWidth\");\n   return false;\n}\n   \nbool OOFAwareWidget::isPossibleOOFContainer (int oofmIndex)\n{\n   return oofmIndex != OOFM_FLOATS;\n}\n\nbool OOFAwareWidget::isPossibleOOFContainerParent (int oofmIndex)\n{\n   return oofmIndex != OOFM_FLOATS;\n}\n\nbool OOFAwareWidget::adjustExtraSpaceWhenCorrectingRequisitionByOOF ()\n{\n   return true;\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/oofawarewidget.hh",
    "content": "#ifndef __DW_OOFAWAREWIDGET_HH__\n#define __DW_OOFAWAREWIDGET_HH__\n\n#include \"core.hh\"\n#include \"outofflowmgr.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\n/**\n * \\brief Base class for widgets which can act as container and\n *     generator for widgets out of flow.\n *\n * (Perhaps it should be diffenciated between the two roles, container\n * and generator, but this would make multiple inheritance necessary.)\n *\n * See \\ref dw-out-of-flow for an overview.\n *\n * Requirements for sub classes (in most cases refer to dw::Textblock\n * as a good example):\n *\n * - A sub class should at least take care to call these methods at the\n *   respective points:\n *\n *   - dw::oof::OOFAwareWidget::correctRequisitionByOOF (from\n *     dw::core::Widget::getExtremesImpl)\n *   - dw::oof::OOFAwareWidget::correctExtremesByOOF (from\n *     dw::core::Widget::sizeRequestImpl)\n *   - dw::oof::OOFAwareWidget::sizeAllocateStart\n *   - dw::oof::OOFAwareWidget::sizeAllocateEnd (latter two from\n *     dw::core::Widget::sizeAllocateImpl)\n *   - dw::oof::OOFAwareWidget::containerSizeChangedForChildrenOOF\n *     (from dw::core::Widget::containerSizeChangedForChildren)\n *   - dw::oof::OOFAwareWidget::drawOOF (from dw::core::Widget::draw)\n *   - dw::oof::OOFAwareWidget::getWidgetOOFAtPoint (from\n *     dw::core::Widget::getWidgetAtPoint)\n *\n * - Implementations of dw::core::Widget::getAvailWidthOfChild and\n *   dw::core::Widget::getAvailHeightOfChild have to distinguish\n *   between widgets in flow and out of flow; general pattern:\n *\n *    \\code\n * if (isWidgetOOF (child) && getWidgetOutOfFlowMgr(child) &&\n *     getWidgetOutOfFlowMgr(child)->dealingWithSizeOfChild (child))\n *    width =\n *      getWidgetOutOfFlowMgr(child)->getAvailWidthOfChild (child,forceValue);\n * else {\n *    // ... specific implementation ...\n *    \\endcode\n *\n *   See also implementations of dw::Textblock and dw::Table. (Open\n *   issue: What about dw::core::Widget::correctRequisitionOfChild and\n *   dw::core::Widget::correctExtremesOfChild? Currently, all widgets\n *   are used the default implementation.)\n *\n * - Iterators have to consider widgets out of flow;\n *   dw::oof::OOFAwareWidget::OOFAwareWidgetIterator is recommended as\n *   base class.\n *\n * - dw::core::Widget::parentRef has to be set for widgets in flow; if\n *   not used further, a simple *makeParentRefInFlow(0)* is sufficient\n *   (as dw::Table::addCell does). Widgets which are only containers,\n *   but not generators, do not have to care about widgets out of\n *   flow in this regard.\n *\n * For both generators and containers of floats (which is only\n * implemented by dw::Textblock) it gets a bit more complicated.\n *\n * \\todo Currently, on the level of dw::oof::OOFAwareWidget, nothing\n * is done about dw::core::Widget::markSizeChange and\n * dw::core::Widget::markExtremesChange. This does not matter, though:\n * dw::Textblock takes care of these, and dw::Table is only connected\n * to subclasses of dw::oof::OOFPositionedMgr, which do care about\n * these. However, this should be considered for completeness.\n */\nclass OOFAwareWidget: public core::Widget\n{ \nprotected:\n   enum { OOFM_FLOATS, OOFM_ABSOLUTE, OOFM_RELATIVE, OOFM_FIXED, NUM_OOFM };\n   static const char *OOFM_NAME[NUM_OOFM];\n   enum { PARENT_REF_OOFM_BITS = 3,\n          PARENT_REF_OOFM_MASK = (1 << PARENT_REF_OOFM_BITS) - 1 };\n\n   class OOFAwareWidgetIterator: public core::Iterator\n   {\n   private:\n      enum { NUM_SECTIONS = NUM_OOFM + 1 };\n      int sectionIndex; // 0 means in flow, otherwise OOFM index + 1\n      int index;\n\n      int numParts (int sectionIndex, int numContentsInFlow = -1);\n      void getPart (int sectionIndex, int index, core::Content *content);\n\n   protected:\n      virtual int numContentsInFlow () = 0;\n      virtual void getContentInFlow (int index, core::Content *content) = 0;\n\n      void setValues (int sectionIndex, int index);\n      inline void cloneValues (OOFAwareWidgetIterator *other)\n      { other->setValues (sectionIndex, index); }         \n\n      inline bool inFlow () { return sectionIndex == 0; }\n      inline int getInFlowIndex () { assert (inFlow ()); return index; }\n      void highlightOOF (int start, int end, core::HighlightLayer layer);\n      void unhighlightOOF (int direction, core::HighlightLayer layer);\n      void getAllocationOOF (int start, int end, core::Allocation *allocation);\n\n   public:\n      OOFAwareWidgetIterator (OOFAwareWidget *widget, core::Content::Type mask,\n                              bool atEnd, int numContentsInFlow);\n\n      void intoStringBuffer(lout::misc::StringBuffer *sb);\n      int compareTo(lout::object::Comparable *other);\n\n      bool next ();\n      bool prev ();\n   };\n\n   inline bool isParentRefOOF (int parentRef)\n   { return parentRef != -1 && (parentRef & PARENT_REF_OOFM_MASK); }\n\n   inline int makeParentRefInFlow (int inFlowSubRef)\n   { return (inFlowSubRef << PARENT_REF_OOFM_BITS); }\n   inline int getParentRefInFlowSubRef (int parentRef)\n   { assert (!isParentRefOOF (parentRef));\n      return parentRef >> PARENT_REF_OOFM_BITS; }\n\n   inline int makeParentRefOOF (int oofmIndex, int oofmSubRef)\n   { return (oofmSubRef << PARENT_REF_OOFM_BITS) | (oofmIndex + 1); }\n   inline int getParentRefOOFSubRef (int parentRef)\n   { assert (isParentRefOOF (parentRef));\n      return parentRef >> PARENT_REF_OOFM_BITS; }\n   inline int getParentRefOOFIndex (int parentRef)\n   { assert (isParentRefOOF (parentRef));\n      return (parentRef & PARENT_REF_OOFM_MASK) - 1; }\n   inline oof::OutOfFlowMgr *getParentRefOutOfFlowMgr (int parentRef)\n   { return outOfFlowMgr[getParentRefOOFIndex (parentRef)]; }\n\n   inline bool isWidgetOOF (Widget *widget)\n   { return isParentRefOOF (widget->parentRef); }\n\n   inline int getWidgetInFlowSubRef (Widget *widget)\n   { return getParentRefInFlowSubRef (widget->parentRef); }\n\n   inline int getWidgetOOFSubRef (Widget *widget)\n   { return getParentRefOOFSubRef (widget->parentRef); }\n   inline int getWidgetOOFIndex (Widget *widget)\n   { return getParentRefOOFIndex (widget->parentRef); }\n   inline oof::OutOfFlowMgr *getWidgetOutOfFlowMgr (Widget *widget)\n   { return getParentRefOutOfFlowMgr (widget->parentRef); }\n\n   OOFAwareWidget *oofContainer[NUM_OOFM];\n   OutOfFlowMgr *outOfFlowMgr[NUM_OOFM];\n   core::Requisition requisitionWithoutOOF;\n\n   inline OutOfFlowMgr *searchOutOfFlowMgr (int oofmIndex)\n   { return oofContainer[oofmIndex] ?\n         oofContainer[oofmIndex]->outOfFlowMgr[oofmIndex] : NULL; }\n\n   static int getOOFMIndex (Widget *widget);\n\n   void initOutOfFlowMgrs ();\n   void correctRequisitionByOOF (core::Requisition *requisition,\n                                 void (*splitHeightFun) (int, int*, int*));\n   void correctExtremesByOOF (core::Extremes *extremes);\n   void sizeAllocateStart (core::Allocation *allocation);\n   void sizeAllocateEnd ();\n   void containerSizeChangedForChildrenOOF ();\n\n   virtual void drawLevel (core::View *view, core::Rectangle *area, int level,\n                           core::DrawingContext *context);\n   void drawOOF (core::View *view, core::Rectangle *area,\n                 core::DrawingContext *context);\n\n   Widget *getWidgetAtPoint (int x, int y,\n                             core::GettingWidgetAtPointContext *context);\n   virtual Widget *getWidgetAtPointLevel (int x, int y, int level,\n                                          core::GettingWidgetAtPointContext\n                                          *context);\n   Widget *getWidgetOOFAtPoint (int x, int y,\n                                core::GettingWidgetAtPointContext *context);\n\n   static bool isOOFContainer (Widget *widget, int oofmIndex);\n\n   void notifySetAsTopLevel();\n   void notifySetParent();\n\n   void removeChild (Widget *child);\n\n   virtual bool adjustExtraSpaceWhenCorrectingRequisitionByOOF ();\n\npublic:\n   enum {\n      SL_START, SL_BACKGROUND, SL_SC_BOTTOM, SL_IN_FLOW, SL_OOF_REF,\n      SL_OOF_CONT, SL_SC_TOP, SL_END };\n\n   static int CLASS_ID;\n\n   OOFAwareWidget ();\n   ~OOFAwareWidget ();\n\n   static const char *stackingLevelText (int level);\n\n   static inline bool testStyleFloat (core::style::Style *style)\n   { return style->vloat != core::style::FLOAT_NONE; }\n\n   static inline bool testStyleAbsolutelyPositioned (core::style::Style *style)\n   { return IMPL_POS && style->position == core::style::POSITION_ABSOLUTE; }\n   static inline bool testStyleFixedlyPositioned (core::style::Style *style)\n   { return IMPL_POS && style->position == core::style::POSITION_FIXED; }\n   static inline bool testStyleRelativelyPositioned (core::style::Style *style)\n   { return IMPL_POS && style->position == core::style::POSITION_RELATIVE; }\n\n   static inline bool testStylePositioned (core::style::Style *style)\n   { return testStyleAbsolutelyPositioned (style) ||\n         testStyleRelativelyPositioned (style) ||\n         testStyleFixedlyPositioned (style); }\n\n   static inline bool testStyleOutOfFlow (core::style::Style *style)\n   {  // Second part is equivalent to testStylePositioned(), but we still keep\n      // the two seperately.\n      return testStyleFloat (style) || testStyleAbsolutelyPositioned (style)\n         || testStyleRelativelyPositioned (style)\n         || testStyleFixedlyPositioned (style); }\n   \n   static inline bool testWidgetFloat (Widget *widget)\n   { return testStyleFloat (widget->getStyle ()); }\n\n   static inline bool testWidgetAbsolutelyPositioned (Widget *widget)\n   { return testStyleAbsolutelyPositioned (widget->getStyle ()); }\n   static inline bool testWidgetFixedlyPositioned (Widget *widget)\n   { return testStyleFixedlyPositioned (widget->getStyle ()); }\n   static inline bool testWidgetRelativelyPositioned (Widget *widget)\n   { return testStyleRelativelyPositioned (widget->getStyle ()); }\n\n   static inline bool testWidgetPositioned (Widget *widget)\n   { return testStylePositioned (widget->getStyle ()); }\n\n   static inline bool testWidgetOutOfFlow (Widget *widget)\n   { return testStyleOutOfFlow (widget->getStyle ()); }\n\n   inline core::Requisition *getRequisitionWithoutOOF ()\n   { return &requisitionWithoutOOF; }\n\n   bool doesWidgetOOFInterruptDrawing (Widget *widget);\n\n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n\n   /**\n    * Update content in flow, down from `ref`. Uses e. g. when floats sizes have\n    * changed.\n    */\n   virtual void updateReference (int ref);\n\n   /**\n    * Called by an implementation of dw::oof::OutOfFlowMgr (actually only\n    * OOFPosRelMgr) for the generator of a widget out of flow, when the\n    * reference size has changed. (The size of the reference is 0 * 0 for all\n    * other implementations of dw::oof::OutOfFlowMgr.)\n    */\n   virtual void widgetRefSizeChanged (int externalIndex);\n   \n   /**\n    * Called by an implementation of dw::oof::OutOfFlowMgr when the size of the\n    * container has changed, typically in sizeAllocateEnd.\n    */\n   virtual void oofSizeChanged (bool extremesChanged);\n\n   /**\n    * Return position relative to container, not regarding\n    * margin/border/padding, Called by OOFFloatsMgr to position floats.\n    */\n   virtual int getGeneratorX (int oofmIndex);\n\n   /**\n    * Return position relative to container, not regarding\n    * margin/border/padding, Called by OOFFloatsMgr to position floats.\n    */\n   virtual int getGeneratorY (int oofmIndex);\n\n   /**\n    * Return width including margin/border/padding Called by OOFFloatsMgr to\n    * position floats.\n    */\n   virtual int getGeneratorWidth ();\n\n   virtual int getMaxGeneratorWidth ();\n\n   virtual bool usesMaxGeneratorWidth ();\n   \n   virtual bool isPossibleOOFContainer (int oofmIndex);\n\n   virtual bool isPossibleOOFContainerParent (int oofmIndex);\n};\n\n} // namespace oof\n\n} // namespace dw\n\n#endif // __DW_OOFAWAREWIDGET_HH__\n"
  },
  {
    "path": "dw/oofawarewidget_iterator.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"oofawarewidget.hh\"\n#include \"ooffloatsmgr.hh\"\n#include \"oofposabsmgr.hh\"\n#include \"oofposfixedmgr.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace lout::misc;\nusing namespace lout::object;\n\nnamespace dw {\n\nnamespace oof {\n\n// \"numContentsInFlow\" is passed here to avoid indirectly calling the (virtual)\n// method numContentsInFlow() from the constructor.\nOOFAwareWidget::OOFAwareWidgetIterator::OOFAwareWidgetIterator\n   (OOFAwareWidget *widget, Content::Type mask, bool atEnd,\n    int numContentsInFlow) :\n   Iterator (widget, mask, atEnd)   \n{\n   if (atEnd) {\n      sectionIndex = NUM_SECTIONS - 1;\n      while (sectionIndex >= 0 &&\n             numParts (sectionIndex, numContentsInFlow) == 0)\n         sectionIndex--;\n      index = numParts (sectionIndex, numContentsInFlow);\n   } else {\n      sectionIndex = 0;\n      index = -1;\n   }\n\n   content.type = atEnd ? core::Content::END : core::Content::START;\n}\n\nvoid OOFAwareWidget::OOFAwareWidgetIterator::setValues (int sectionIndex,\n                                                        int index)\n{\n   this->sectionIndex = sectionIndex;\n   this->index = index;\n\n   if (sectionIndex < 0 || index < 0)\n      content.type = core::Content::START;\n   else if (sectionIndex >= NUM_SECTIONS || index >= numParts (sectionIndex))\n      content.type = core::Content::END;\n   else\n      getPart (sectionIndex, index, &content);\n}\n\nint OOFAwareWidget::OOFAwareWidgetIterator::numParts (int sectionIndex,\n                                                      int numContentsInFlow)\n{\n   DBG_OBJ_ENTER_O (\"iterator\", 0, getWidget(), \"numParts\", \"%d, %d\",\n                    sectionIndex, numContentsInFlow);\n\n   OOFAwareWidget *widget = (OOFAwareWidget*)getWidget();\n   int result;\n\n   if (sectionIndex < 0 || sectionIndex > NUM_SECTIONS) {\n      DBG_OBJ_MARKF_O(\"iterator\", 0, getWidget(), \"invalid sectionIndex %d\",\n                      sectionIndex);\n      result = 0;\n   } else if (sectionIndex == 0)\n      result = numContentsInFlow == -1 ?\n         this->numContentsInFlow () : numContentsInFlow;\n   else\n      result = widget->outOfFlowMgr[sectionIndex - 1] ?\n         widget->outOfFlowMgr[sectionIndex - 1]->getNumWidgets () : 0;\n\n   DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget(), \"=> %d\", result);\n   DBG_OBJ_LEAVE_O (getWidget());\n   return result;\n}\n\nvoid OOFAwareWidget::OOFAwareWidgetIterator::getPart (int sectionIndex,\n                                                      int index,\n                                                      Content *content)\n{\n   OOFAwareWidget *widget = (OOFAwareWidget*)getWidget();\n\n   if (sectionIndex == 0)\n      getContentInFlow (index, content);\n   else {\n      content->type = Content::WIDGET_OOF_CONT;\n      content->widget =\n         widget->outOfFlowMgr[sectionIndex - 1]->getWidget (index);\n   }\n}\n\nvoid OOFAwareWidget::OOFAwareWidgetIterator::intoStringBuffer (StringBuffer *sb)\n{\n   Iterator::intoStringBuffer (sb);\n   sb->append (\", sectionIndex = \");\n   sb->appendInt (sectionIndex);\n   sb->append (\", index = \");\n   sb->appendInt (index);\n}\n\nint OOFAwareWidget::OOFAwareWidgetIterator::compareTo (Comparable *other)\n{\n   OOFAwareWidgetIterator *otherTI = (OOFAwareWidgetIterator*)other;\n\n   if (sectionIndex != otherTI->sectionIndex)\n      return sectionIndex - otherTI->sectionIndex;\n   else\n      return index - otherTI->index;\n}\n\nbool OOFAwareWidget::OOFAwareWidgetIterator::next ()\n{\n   DBG_OBJ_ENTER0_O (\"iterator\", 0, getWidget (),\n                     \"OOFAwareWidgetIterator/next\");\n\n   DBG_IF_RTFL {\n      StringBuffer sb;\n      intoStringBuffer (&sb);\n      DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget (),\n                      \"initial value: %s; sectionIndex = %d, index = %d\",\n                      sb.getChars (), sectionIndex, index);\n   }\n\n   bool found = false;\n\n   if (content.type != Content::END) {\n      while (!found) {\n         ++index;\n\n         if (sectionIndex >= 0 && sectionIndex < NUM_SECTIONS &&\n             index >= 0 && index < numParts (sectionIndex)) {\n            getPart (sectionIndex, index, &content);\n         } else {\n            while (++sectionIndex < NUM_SECTIONS &&\n                   numParts (sectionIndex) == 0) ;\n            \n            if (sectionIndex >= NUM_SECTIONS) {\n               content.type = Content::END;\n               break;\n            } else {\n               index = -1;\n               continue;\n            }\n         }         \n         found = (content.type & getMask());\n      }\n   }\n\n   DBG_IF_RTFL {\n      StringBuffer sb;\n      intoStringBuffer (&sb);\n      DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget (), \"final value: %s\",\n                      sb.getChars ());\n   }\n\n   DBG_OBJ_LEAVE_VAL_O (getWidget (), \"%s\", boolToStr (found));\n   return found;\n}\n\nbool OOFAwareWidget::OOFAwareWidgetIterator::prev ()\n{\n   DBG_OBJ_ENTER0_O (\"iterator\", 0, getWidget (),\n                     \"OOFAwareWidgetIterator/prev\");\n\n   DBG_IF_RTFL {\n      StringBuffer sb;\n      intoStringBuffer (&sb);\n      DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget (),\n                      \"initial value: %s; sectionIndex = %d, index = %d\",\n                      sb.getChars (), sectionIndex, index);\n   }\n\n   bool found = false;\n\n   if (content.type != Content::START) {\n      while (!found) {\n         index--;\n\n         if (sectionIndex >= 0 && sectionIndex < NUM_SECTIONS &&\n             index >= 0 && index < numParts (sectionIndex)) {\n            getPart (sectionIndex, index, &content);\n         } else {\n            while (--sectionIndex >= 0 && numParts (sectionIndex) == 0) ;\n\n            if (sectionIndex < 0) {\n               content.type = Content::START;\n               break;\n            } else {\n               index = numParts (sectionIndex);\n               continue;\n            }\n         }\n         found = (content.type & getMask());\n      }\n   }\n\n   DBG_IF_RTFL {\n      StringBuffer sb;\n      intoStringBuffer (&sb);\n      DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget (), \"final value: %s\",\n                      sb.getChars ());\n   }\n\n   DBG_OBJ_LEAVE_VAL_O (getWidget (), \"%s\", boolToStr (found));\n   return found;\n}\n\nvoid OOFAwareWidget::OOFAwareWidgetIterator::highlightOOF (int start, int end,\n                                                           HighlightLayer layer)\n{\n   // TODO What about OOF widgets?\n}\n\nvoid OOFAwareWidget::OOFAwareWidgetIterator::unhighlightOOF (int direction,\n                                                             HighlightLayer\n                                                             layer)\n{\n   // TODO What about OOF widgets?\n}\n\nvoid OOFAwareWidget::OOFAwareWidgetIterator::getAllocationOOF (int start,\n                                                               int end,\n                                                               Allocation\n                                                               *allocation)\n{\n   // TODO Consider start and end?\n   OOFAwareWidget *widget = (OOFAwareWidget*)getWidget();\n   *allocation = *(widget->outOfFlowMgr[sectionIndex - 1]\n                   ->getWidget(index)->getAllocation());\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/ooffloatsmgr.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2013-2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"ooffloatsmgr.hh\"\n#include \"oofawarewidget.hh\"\n#include \"../lout/debug.hh\"\n\n#include <limits.h>\n\nusing namespace lout::object;\nusing namespace lout::container::typed;\nusing namespace lout::misc;\nusing namespace dw::core;\nusing namespace dw::core::style;\n\nnamespace dw {\n\nnamespace oof {\n\nOOFFloatsMgr::WidgetInfo::WidgetInfo (OOFFloatsMgr *oofm, Widget *widget)\n{\n   this->oofm = oofm;\n   this->widget = widget;\n}\n\n// ----------------------------------------------------------------------\n\nOOFFloatsMgr::Float::Float (OOFFloatsMgr *oofm, Widget *widget,\n                            OOFAwareWidget *generatingBlock, int externalIndex)\n   : WidgetInfo (oofm, widget)\n{\n   this->generator = generatingBlock;\n   this->externalIndex = externalIndex;\n\n   yReq = yReal = size.width = size.ascent = size.descent = 0;\n   dirty = true;\n   index = -1;\n\n   // Sometimes a float with widget = NULL is created as a key; this\n   // is not interesting for RTFL.\n   if (widget) {\n      DBG_OBJ_SET_PTR_O (widget, \"<Float>.generatingBlock\", generatingBlock);\n      DBG_OBJ_SET_NUM_O (widget, \"<Float>.externalIndex\", externalIndex);\n      DBG_OBJ_SET_NUM_O (widget, \"<Float>.yReq\", yReq);\n      DBG_OBJ_SET_NUM_O (widget, \"<Float>.yReal\", yReal);\n      DBG_OBJ_SET_NUM_O (widget, \"<Float>.size.width\", size.width);\n      DBG_OBJ_SET_NUM_O (widget, \"<Float>.size.ascent\", size.ascent);\n      DBG_OBJ_SET_NUM_O (widget, \"<Float>.size.descent\", size.descent);\n      DBG_OBJ_SET_BOOL_O (widget, \"<Float>.dirty\", dirty);\n   }\n}\n\nvoid OOFFloatsMgr::Float::intoStringBuffer(StringBuffer *sb)\n{\n   sb->append (\"{ widget = \");\n   sb->appendPointer (getWidget ());\n\n   if (getWidget ()) {\n      sb->append (\" (\");\n      sb->append (getWidget()->getClassName ());\n      sb->append (\")\");\n   }\n\n   sb->append (\", index = \");\n   sb->appendInt (index);\n   sb->append (\", sideSpanningIndex = \");\n   sb->appendInt (sideSpanningIndex);\n   sb->append (\", generator = \");\n   sb->appendPointer (generator);\n   sb->append (\", yReq = \");\n   sb->appendInt (yReq);\n   sb->append (\", yReal = \");\n   sb->appendInt (yReal);\n   sb->append (\", size = { \");\n   sb->appendInt (size.width);\n   sb->append (\" * \");\n   sb->appendInt (size.ascent);\n   sb->append (\" + \");\n   sb->appendInt (size.descent);\n   sb->append (\" }, dirty = \");\n   sb->appendBool (dirty);\n   sb->append (\", sizeChangedSinceLastAllocation = \");\n   sb->append (\" }\");\n}\n\n/**\n * `y` is given relative to the container.\n */\nbool OOFFloatsMgr::Float::covers (int y, int h)\n{\n   DBG_OBJ_ENTER_O (\"border\", 0, getOOFFloatsMgr (), \"covers\",\n                    \"%d, %d [vloat: %p]\", y, h, getWidget ());\n\n   getOOFFloatsMgr()->ensureFloatSize (this);\n   bool b = yReal + size.ascent + size.descent > y && yReal < y + h;\n\n   DBG_OBJ_LEAVE_VAL_O (getOOFFloatsMgr (), \"%s\", b ? \"true\" : \"false\");\n   return b;\n}\n\nint OOFFloatsMgr::Float::ComparePosition::compare (Object *o1, Object *o2)\n{\n   return ((Float*)o1)->yReal - ((Float*)o2)->yReal;\n}\n\nint OOFFloatsMgr::Float::CompareSideSpanningIndex::compare (Object *o1,\n                                                            Object *o2)\n{\n   return ((Float*)o1)->sideSpanningIndex - ((Float*)o2)->sideSpanningIndex;\n}\n\nint OOFFloatsMgr::Float::CompareGBAndExtIndex::compare (Object *o1, Object *o2)\n{\n   Float *f1 = (Float*)o1, *f2 = (Float*)o2;\n   int r = -123; // Compiler happiness: GCC 4.7 does not handle this?;\n\n   DBG_OBJ_ENTER_O (\"border\", 1, oofm, \"CompareGBAndExtIndex::compare\",\n                    \"#%d -> %p/%d, #%d -> %p/#%d\",\n                    f1->index, f1->generator, f1->externalIndex,\n                    f2->index, f2->generator, f2->externalIndex);\n\n   if (f1->generator == f2->generator) {\n      r = f1->externalIndex - f2->externalIndex;\n      DBG_OBJ_MSGF_O (\"border\", 2, oofm,\n                      \"(a) generating blocks equal => %d - %d = %d\",\n                      f1->externalIndex, f2->externalIndex, r);\n   } else {\n      TBInfo *t1 = oofm->getOOFAwareWidget (f1->generator),\n         *t2 = oofm->getOOFAwareWidget (f2->generator);\n      bool rdef = false;\n\n      for (TBInfo *t = t1; t != NULL; t = t->parent)\n         if (t->parent == t2) {\n            rdef = true;\n            r = t->parentExtIndex - f2->externalIndex;\n            DBG_OBJ_MSGF_O (\"border\", 2, oofm,\n                            \"(b) %p is an achestor of %p; direct child is \"\n                            \"%p (%d) => %d - %d = %d\\n\",\n                            t2->getOOFAwareWidget (), t1->getOOFAwareWidget (),\n                            t->getOOFAwareWidget (), t->parentExtIndex,\n                            t->parentExtIndex, f2->externalIndex, r);\n         }\n\n      for (TBInfo *t = t2; !rdef && t != NULL; t = t->parent)\n         if (t->parent == t1) {\n            r = f1->externalIndex - t->parentExtIndex;\n            rdef = true;\n            DBG_OBJ_MSGF_O (\"border\", 2, oofm,\n                            \"(c) %p is an achestor of %p; direct child is %p \"\n                            \"(%d) => %d - %d = %d\\n\",\n                            t1->getOOFAwareWidget (), t2->getOOFAwareWidget (),\n                            t->getOOFAwareWidget (), t->parentExtIndex,\n                            f1->externalIndex, t->parentExtIndex, r);\n         }\n\n      if (!rdef) {\n         r = t1->index - t2->index;\n         DBG_OBJ_MSGF_O (\"border\", 2, oofm, \"(d) other => %d - %d = %d\",\n                         t1->index, t2->index, r);\n      }\n   }\n\n   DBG_OBJ_LEAVE_VAL_O (oofm, \"%d\", r);\n   return r;\n}\n\nint OOFFloatsMgr::SortedFloatsVector::findFloatIndex (OOFAwareWidget *lastGB,\n                                                      int lastExtIndex)\n{\n   DBG_OBJ_ENTER_O (\"border\", 0, oofm, \"findFloatIndex\", \"%p, %d\",\n                    lastGB, lastExtIndex);\n\n   Float key (oofm, NULL, lastGB, lastExtIndex);\n   key.index = -1; // for debugging\n   Float::CompareGBAndExtIndex comparator (oofm);\n   int i = bsearch (&key, false, &comparator);\n\n   // At position i is the next larger element, so element i should\n   // not included, but i - 1 returned; except if the exact element is\n   // found: then include it and so return i.\n   int r;\n   if (i == size())\n      r = i - 1;\n   else {\n      Float *f = get (i);\n      if (comparator.compare (f, &key) == 0)\n         r = i;\n      else\n         r = i - 1;\n   }\n\n   //printf (\"[%p] findFloatIndex (%p, %d) => i = %d, r = %d (size = %d); \"\n   //        \"in %s list %p on the %s side\\n\",\n   //        oofm->container, lastGB, lastExtIndex, i, r, size (),\n   //        type == GB ? \"GB\" : \"CB\", this, side == LEFT ? \"left\" : \"right\");\n\n   //for (int i = 0; i < size (); i++) {\n   //   Float *f = get(i);\n   //   TBInfo *t = oofm->getOOFAwareWidget(f->generatingBlock);\n   //   printf (\"   %d: (%p [%d, %p], %d)\\n\", i, f->generatingBlock,\n   //           t->index, t->parent ? t->parent->textblock : NULL,\n   //           get(i)->externalIndex);\n   //}\n\n   DBG_OBJ_LEAVE_VAL_O (oofm, \"%d\", r);\n   return r;\n}\n\n/**\n * `y` is given relative to the container.\n */\nint OOFFloatsMgr::SortedFloatsVector::find (int y, int start, int end)\n{\n   DBG_OBJ_ENTER_O (\"border\", 0, oofm, \"find\", \"%d, %d, %d\", y, start, end);\n\n   Float key (oofm, NULL, NULL, 0);\n   key.yReal = y;\n   Float::ComparePosition comparator;\n   int result = bsearch (&key, false, start, end, &comparator);\n\n   DBG_OBJ_LEAVE_VAL_O (oofm, \"%d\", result);\n   return result;\n}\n\nint OOFFloatsMgr::SortedFloatsVector::findFirst (int y, int h,\n                                                 OOFAwareWidget *lastGB,\n                                                 int lastExtIndex,\n                                                 int *lastReturn)\n{\n   DBG_OBJ_ENTER_O (\"border\", 0, oofm, \"findFirst\", \"%d, %d, %p, %d\",\n                    y, h, lastGB, lastExtIndex);\n\n   int last = findFloatIndex (lastGB, lastExtIndex);\n   DBG_OBJ_MSGF_O (\"border\", 1, oofm, \"last = %d\", last);\n   assert (last < size());\n\n   // If the caller wants to reuse this value:\n   if (lastReturn)\n      *lastReturn = last;\n\n   int i = find (y, 0, last), result;\n   DBG_OBJ_MSGF_O (\"border\", 1, oofm, \"i = %d\", i);\n\n   // Note: The smallest value of \"i\" is 0, which means that \"y\" is before or\n   // equal to the first float. The largest value is \"last + 1\", which means\n   // that \"y\" is after the last float. In both cases, the first or last,\n   // respectively, float is a candidate. Generally, both floats, before and\n   // at the search position, are candidates.\n\n   if (i > 0 && get(i - 1)->covers (y, h))\n      result = i - 1;\n   else if (i <= last && get(i)->covers (y, h))\n      result = i;\n   else\n      result = -1;\n\n   DBG_OBJ_LEAVE_VAL_O (oofm, \"%d\", result);\n   return result;\n}\n\nint OOFFloatsMgr::SortedFloatsVector::findLastBeforeSideSpanningIndex\n   (int sideSpanningIndex)\n{\n   OOFFloatsMgr::Float::CompareSideSpanningIndex comparator;\n   Float key (NULL, NULL, NULL, 0);\n   key.sideSpanningIndex = sideSpanningIndex;\n   return bsearch (&key, false, &comparator) - 1;\n}\n\nvoid OOFFloatsMgr::SortedFloatsVector::put (Float *vloat)\n{\n   lout::container::typed::Vector<Float>::put (vloat);\n   vloat->index = size() - 1;\n}\n\nint OOFFloatsMgr::TBInfo::ComparePosition::compare (Object *o1, Object *o2)\n{\n   TBInfo *tbInfo1 = (TBInfo*)o1, *tbInfo2 = (TBInfo*)o2;\n   int y1 = tbInfo1->getOOFAwareWidget () == NULL ? tbInfo1->y :\n      tbInfo1->getOOFAwareWidget()->getGeneratorY (oofmIndex);\n   int y2 = tbInfo2->getOOFAwareWidget () == NULL ? tbInfo2->y :\n      tbInfo2->getOOFAwareWidget()->getGeneratorY (oofmIndex);\n   return y1 - y2;\n}\n\nOOFFloatsMgr::TBInfo::TBInfo (OOFFloatsMgr *oofm, OOFAwareWidget *textblock,\n                              TBInfo *parent, int parentExtIndex) :\n   WidgetInfo (oofm, textblock)\n{\n   this->parent = parent;\n   this->parentExtIndex = parentExtIndex;\n\n   leftFloats = new Vector<Float> (1, false);\n   rightFloats = new Vector<Float> (1, false);\n}\n\nOOFFloatsMgr::TBInfo::~TBInfo ()\n{\n   delete leftFloats;\n   delete rightFloats;\n}\n\nOOFFloatsMgr::OOFFloatsMgr (OOFAwareWidget *container, int oofmIndex)\n{\n   DBG_OBJ_CREATE (\"dw::oof::OOFFloatsMgr\");\n\n   this->container = container;\n   this->oofmIndex = oofmIndex;\n\n   leftFloats = new SortedFloatsVector (this, LEFT, true);\n   rightFloats = new SortedFloatsVector (this, RIGHT, true);\n\n   DBG_OBJ_SET_NUM (\"leftFloats.size\", leftFloats->size());\n   DBG_OBJ_SET_NUM (\"rightFloats.size\", rightFloats->size());\n\n   floatsByWidget = new HashTable <TypedPointer <Widget>, Float> (true, false);\n\n   tbInfos = new Vector<TBInfo> (1, false);\n   tbInfosByOOFAwareWidget =\n      new HashTable <TypedPointer <OOFAwareWidget>, TBInfo> (true, true);\n\n   leftFloatsMark = rightFloatsMark = 0;\n   lastLeftTBIndex = lastRightTBIndex = 0;\n\n   SizeChanged = true;\n   DBG_OBJ_SET_BOOL (\"SizeChanged\", SizeChanged);\n\n   containerAllocation = *(container->getAllocation());\n\n   addWidgetInFlow (this->container, NULL, 0);\n}\n\nOOFFloatsMgr::~OOFFloatsMgr ()\n{\n   // Order is important: tbInfosByOOFAwareWidget is owner of the instances\n   // of TBInfo.tbInfosByOOFAwareWidget. Also, leftFloats and rightFloats are\n   // owners of the floats.\n   delete tbInfos;\n   delete tbInfosByOOFAwareWidget;\n\n   delete floatsByWidget;\n\n   delete leftFloats;\n   delete rightFloats;\n   \n   DBG_OBJ_DELETE ();\n}\n\nvoid OOFFloatsMgr::sizeAllocateStart (OOFAwareWidget *caller,\n                                      Allocation *allocation)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"sizeAllocateStart\",\n                  \"%p, (%d, %d, %d * (%d + %d))\",\n                  caller, allocation->x, allocation->y, allocation->width,\n                  allocation->ascent, allocation->descent);\n\n   if (caller == container)\n      containerAllocation = *allocation;\n   \n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFFloatsMgr::sizeAllocateEnd (OOFAwareWidget *caller)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"sizeAllocateEnd\", \"%p\", caller);\n\n   if (caller == container) {\n      sizeAllocateFloats (LEFT);\n      sizeAllocateFloats (RIGHT);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFFloatsMgr::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n\n   for (int i = 0; i < leftFloats->size (); i++)\n      leftFloats->get(i)->getWidget()->containerSizeChanged ();\n   for (int i = 0; i < rightFloats->size (); i++)\n      rightFloats->get(i)->getWidget()->containerSizeChanged ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFFloatsMgr::sizeAllocateFloats (Side side)\n{\n   SortedFloatsVector *list = side == LEFT ? leftFloats : rightFloats;\n\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"sizeAllocateFloats\", \"%s\",\n                  side == LEFT ? \"LEFT\" : \"RIGHT\");\n\n   for (int i = 0; i < list->size (); i++) {\n      Float *vloat = list->get(i);\n      ensureFloatSize (vloat);\n\n      Allocation childAllocation;\n      childAllocation.x = containerAllocation.x + calcFloatX (vloat);\n      childAllocation.y = containerAllocation.y + vloat->yReal;\n      childAllocation.width = vloat->size.width;\n      childAllocation.ascent = vloat->size.ascent;\n      childAllocation.descent = vloat->size.descent;\n\n      vloat->getWidget()->sizeAllocate (&childAllocation);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * \\brief Return position of a float relative to the container.\n */\nint OOFFloatsMgr::calcFloatX (Float *vloat)\n{\n   DBG_OBJ_ENTER (\"resize.common\", 0, \"calcFloatX\", \"%p\", vloat->getWidget ());\n   int x, effGeneratorWidth;\n   OOFAwareWidget *generator = vloat->generator;\n\n   ensureFloatSize (vloat);\n\n   switch (vloat->getWidget()->getStyle()->vloat) {\n   case FLOAT_LEFT:\n      // Left floats are always aligned on the left side of the generator\n      // (content, not allocation) ...\n      x = generator->getGeneratorX (oofmIndex)\n         + generator->getStyle()->boxOffsetX();\n\n      // ... but when the float exceeds the line break width of the container,\n      // it is corrected (but not left of the container).  This way, we save\n      // space and, especially within tables, avoid some problems.\n      if (x + vloat->size.width > container->getMaxGeneratorWidth ())\n         x = max (0, container->getMaxGeneratorWidth () - vloat->size.width);\n      break;\n\n   case FLOAT_RIGHT:\n      // Similar for right floats, but in this case, floats are shifted to the\n      // right when they are too big (instead of shifting the generator to the\n      // right).\n\n      // (The following code for calculating effGeneratorWidth, is quite\n      // specific for textblocks; this also applies for the comments. Both may\n      // be generalized, but actually, only textblocks play a role here.)\n   \n      if (vloat->generator->usesMaxGeneratorWidth ())\n         // For most textblocks, the line break width is used for calculating\n         // the x position. (This changed for GROWS, where the width of a\n         // textblock is often smaller that the line break.)\n         effGeneratorWidth = vloat->generator->getMaxGeneratorWidth ();\n      else\n         // For some textblocks, like inline blocks, the line break width would\n         // be too large for right floats in some cases.\n         //\n         //  (i) Consider a small inline block with only a few words in one\n         //      line, narrower that line break width minus float width. In this\n         //      case, the sum should be used.\n         //\n         // (ii) If there is more than one line, the line break will already be\n         //      exceeded, and so be smaller that GB width + float width.\n         effGeneratorWidth =\n            min (vloat->generator->getGeneratorWidth () + vloat->size.width,\n                 vloat->generator->getMaxGeneratorWidth ());\n\n      x = max (generator->getGeneratorX (oofmIndex) + effGeneratorWidth\n               - vloat->size.width - generator->getStyle()->boxRestWidth(),\n               // Do not exceed container allocation:\n               0);\n      break;\n\n   default:\n      assertNotReached ();\n      x = 0;\n      break;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", x);\n   return x;\n}\n\nvoid OOFFloatsMgr::draw (View *view, Rectangle *area, DrawingContext *context)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"draw\", \"%d, %d, %d * %d\",\n                  area->x, area->y, area->width, area->height);\n\n   drawFloats (leftFloats, view, area, context);\n   drawFloats (rightFloats, view, area, context);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFFloatsMgr::drawFloats (SortedFloatsVector *list, View *view,\n                               Rectangle *area, DrawingContext *context)\n{\n   // This could be improved, since the list is sorted: search the\n   // first float fitting into the area, and iterate until one is\n   // found below the area.\n\n   for (int i = 0; i < list->size(); i++) {\n      Float *vloat = list->get(i);\n      Widget *childWidget = vloat->getWidget ();\n     \n      Rectangle childArea;\n      if (!context->hasWidgetBeenProcessedAsInterruption (childWidget) &&\n         !StackingContextMgr::handledByStackingContextMgr (childWidget) &&\n          childWidget->intersects (container, area, &childArea))\n         childWidget->draw (view, &childArea, context);\n   }\n}\n\nvoid OOFFloatsMgr::addWidgetInFlow (OOFAwareWidget *textblock,\n                                    OOFAwareWidget *parentBlock,\n                                    int externalIndex)\n{\n   //printf (\"[%p] addWidgetInFlow (%p, %p, %d)\\n\",\n   //        container, textblock, parentBlock, externalIndex);\n\n   TBInfo *tbInfo =\n      new TBInfo (this, textblock,\n                  parentBlock ? getOOFAwareWidget (parentBlock) : NULL,\n                  externalIndex);\n   tbInfo->index = tbInfos->size();\n\n   tbInfos->put (tbInfo);\n   tbInfosByOOFAwareWidget->put (new TypedPointer<OOFAwareWidget> (textblock),\n                                 tbInfo);\n}\n\nint OOFFloatsMgr::addWidgetOOF (Widget *widget, OOFAwareWidget *generatingBlock,\n                                 int externalIndex)\n{\n   DBG_OBJ_ENTER (\"construct.oofm\", 0, \"addWidgetOOF\", \"%p, %p, %d\",\n                  widget, generatingBlock, externalIndex);\n\n   int subRef=0;\n\n   TBInfo *tbInfo = getOOFAwareWidget (generatingBlock);\n   Float *vloat = new Float (this, widget, generatingBlock, externalIndex);\n\n   // Note: Putting the float first in the GB list, and then, possibly\n   // into the CB list (in that order) will trigger setting\n   // Float::inCBList to the right value.\n\n   switch (widget->getStyle()->vloat) {\n   case FLOAT_LEFT:\n      leftFloats->put (vloat);\n      DBG_OBJ_SET_NUM (\"leftFloats.size\", leftFloats->size());\n      DBG_OBJ_ARRATTRSET_PTR (\"leftFloats\", leftFloats->size() - 1,\n                              \"widget\", vloat->getWidget ());\n      tbInfo->leftFloats->put (vloat);\n      \n      subRef = createSubRefLeftFloat (leftFloats->size() - 1);\n\n      break;\n      \n   case FLOAT_RIGHT:\n      rightFloats->put (vloat);\n      DBG_OBJ_SET_NUM (\"rightFloats.size\", rightFloats->size());\n      DBG_OBJ_ARRATTRSET_PTR (\"rightFloats\", rightFloats->size() - 1,\n                              \"widget\", vloat->getWidget ());\n      tbInfo->rightFloats->put (vloat);\n      \n      subRef = createSubRefRightFloat (rightFloats->size() - 1);\n      break;\n\n   default:\n      assertNotReached();\n   }\n\n   // \"sideSpanningIndex\" is only compared, so this simple assignment\n   // is sufficient; differenciation between GB and CB lists is not\n   // neccessary.\n   vloat->sideSpanningIndex =\n      leftFloats->size() + rightFloats->size() - 1;\n      \n   floatsByWidget->put (new TypedPointer<Widget> (widget), vloat);\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", subRef);\n   return subRef;\n}\n\nvoid OOFFloatsMgr::calcWidgetRefSize (Widget *widget, Requisition *size)\n{\n   size->width = size->ascent = size->descent = 0;\n}\n\nvoid OOFFloatsMgr::moveExternalIndices (OOFAwareWidget *generatingBlock,\n                                        int oldStartIndex, int diff)\n{\n   TBInfo *tbInfo = getOOFAwareWidget (generatingBlock);\n   moveExternalIndices (tbInfo->leftFloats, oldStartIndex, diff);\n   moveExternalIndices (tbInfo->rightFloats, oldStartIndex, diff);\n}\n\nvoid OOFFloatsMgr::moveExternalIndices (Vector<Float> *list, int oldStartIndex,\n                                        int diff)\n{\n   // Could be faster with binary search, but the number of floats per generator\n   // should be rather small.\n   for (int i = 0; i < list->size (); i++) {\n      Float *vloat = list->get (i);\n      if (vloat->externalIndex >= oldStartIndex) {\n         vloat->externalIndex += diff;\n         DBG_OBJ_SET_NUM_O (vloat->getWidget (), \"<Float>.externalIndex\",\n                            vloat->externalIndex);\n      }\n   }\n}\n\nOOFFloatsMgr::Float *OOFFloatsMgr::findFloatByWidget (Widget *widget)\n{\n   TypedPointer <Widget> key (widget);\n   Float *vloat = floatsByWidget->get (&key);\n   assert (vloat != NULL);\n   return vloat;\n}\n\n/*\n * Currently this is a compound recursion for textblocks:\n * markSizeChange -> updateReference -> queueResize -> markSizeChange\n * One way to see it is as a widget tree coverage problem. i.e. to cover all\n * the nodes that need a resize when a float changes its size.\n * The coverage logic of it is shared between resize code and mark code (here).\n * \n * This implementation works for all the test cases so far. It relies on the\n * fact that Widget::queueResize should be called whenever a widget changes its\n * size. When \"SizeChanged\" is true, we notify the parent, when not, just the\n * following textblocks.\n */\nvoid OOFFloatsMgr::markSizeChange (int ref)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"markSizeChange\", \"%d\", ref);\n\n   // When \"SizeChanged\" is true, we know this float changed its size.\n   // This helps to prune redundant passes.\n   // \"SizeChanged\" is set by getSize(), which is called by sizeRequest().\n\n   SortedFloatsVector *list;\n   list = isSubRefLeftFloat(ref) ? leftFloats : rightFloats;\n   Float *vloat = list->get (getFloatIndexFromSubRef (ref));\n\n   vloat->dirty = true;\n   DBG_OBJ_SET_BOOL_O (vloat->getWidget (), \"<Float>.dirty\", vloat->dirty);\n\n   updateGenerators (vloat);\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * \\brief Update all generators which are affected by a given float.\n */\nvoid OOFFloatsMgr::updateGenerators (Float *vloat)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"updateGenerators\", \"#%d [%p]\",\n                  vloat->index, vloat->getWidget ());\n\n   assert (vloat->getWidget()->getWidgetReference() != NULL);\n\n   int first = getOOFAwareWidget(vloat->generator)->index;\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"updating from %d\", first);\n\n   //printf(\"IN markSizeChange %p ref %d SzCh=%d\\n\", this, ref,\n   //       (int)SizeChanged);\n\n   if (SizeChanged)\n      tbInfos->get(first)->getOOFAwareWidget()\n         ->updateReference (vloat->getWidget()->getWidgetReference()\n                            ->parentRef);\n\n   for (int i = first + 1; i < tbInfos->size(); i++)\n      tbInfos->get(i)->getOOFAwareWidget()->updateReference(0);\n\n   SizeChanged = false; // Done.\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * `y` is given relative to the container.\n */\nint OOFFloatsMgr::findTBInfo (int y)\n{\n   DBG_OBJ_ENTER (\"findTBInfo\", 0, \"findTBInfo\", \"%d\", y);\n\n   TBInfo key (this, NULL, NULL, 0);\n   key.y = y;\n   TBInfo::ComparePosition comparator (oofmIndex);\n   int index = tbInfos->bsearch (&key, false, &comparator);\n\n   // \"bsearch\" returns next greater, but we are interrested in the last which\n   // is less or equal.\n   int result = index > 0 ? index - 1 : index;\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", result);\n   return result;\n}\n\n\nvoid OOFFloatsMgr::markExtremesChange (int ref)\n{\n   // Nothing to do here.\n}\n\nWidget *OOFFloatsMgr::getWidgetAtPoint (int x, int y,\n                                        GettingWidgetAtPointContext *context)\n{\n   Widget *widgetAtPoint = NULL;\n\n   widgetAtPoint = getFloatWidgetAtPoint (rightFloats, x, y, context);\n   if (widgetAtPoint == NULL)\n      widgetAtPoint = getFloatWidgetAtPoint (leftFloats, x, y, context);\n\n   return widgetAtPoint;\n}\n\nWidget *OOFFloatsMgr::getFloatWidgetAtPoint (SortedFloatsVector *list, int x,\n                                             int y,\n                                             GettingWidgetAtPointContext\n                                             *context)\n{\n   // Could use binary search to be faster (similar to drawing).\n   Widget *widgetAtPoint = NULL;\n   \n   for (int i = list->size() - 1; widgetAtPoint == NULL && i >= 0; i--) {\n      Widget *childWidget = list->get(i)->getWidget ();\n      if (!context->hasWidgetBeenProcessedAsInterruption (childWidget) &&\n          !StackingContextMgr::handledByStackingContextMgr (childWidget))\n         widgetAtPoint = childWidget->getWidgetAtPoint (x, y, context);\n   }\n   \n   return widgetAtPoint;\n}\n\nvoid OOFFloatsMgr::tellPosition1 (Widget *widget, int x, int y)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"tellPosition1\", \"%p, %d, %d\",\n                  widget, x, y);\n\n   assert (y >= 0);\n\n   Float *vloat = findFloatByWidget(widget);\n\n   SortedFloatsVector *listSame, *listOpp;\n   Side side;\n   getFloatsListsAndSide (vloat, &listSame, &listOpp, &side);\n   ensureFloatSize (vloat);\n\n   int oldYReal = vloat->yReal;\n\n   // \"yReal\" may change due to collisions (see below).\n   vloat->yReq = vloat->yReal = y;\n\n   DBG_OBJ_SET_NUM_O (vloat->getWidget (), \"<Float>.yReq\", vloat->yReq);\n   DBG_OBJ_SET_NUM_O (vloat->getWidget (), \"<Float>.yReal\", vloat->yReal);\n\n   // Test collisions (on this side). Although there are (rare) cases\n   // where it could make sense, the horizontal dimensions are not\n   // tested; especially since searching and border calculation would\n   // be confused. For this reaspn, only the previous float is\n   // relevant. (Cf. below, collisions on the other side.)\n   int yRealNew;\n   if (vloat->index >= 1 &&\n       collidesV (vloat, listSame->get (vloat->index - 1), &yRealNew)) {\n      vloat->yReal = yRealNew;\n      DBG_OBJ_SET_NUM_O (vloat->getWidget (), \"<Float>.yReal\", vloat->yReal);\n   }\n\n   // Test collisions (on the opposite side). There are cases when\n   // more than one float has to be tested. Consider the following\n   // HTML snippet (\"id\" attribute only used for simple reference\n   // below, as #f1, #f2, and #f3):\n   //\n   //    <div style=\"float:left\" id=\"f1\">\n   //       Left left left left left left left left left left.\n   //    </div>\n   //    <div style=\"float:left\" id=\"f2\">Also left.</div>\n   //    <div style=\"float:right\" id=\"f3\">Right.</div>\n   //\n   // When displayed with a suitable window width (only slightly wider\n   // than the text within #f1), this should look like this:\n   //\n   //    ---------------------------------------------------------\n   //    | Left left left left left left left left left left.    |\n   //    | Also left.                                     Right. |\n   //    ---------------------------------------------------------\n   //\n   // Consider float #f3: a collision test with #f2, considering\n   // vertical dimensions, is positive, but not the test with\n   // horizontal dimensions (because #f2 and #f3 are too\n   // narrow). However, a collision has to be tested with #f1;\n   // otherwise #f3 and #f1 would overlap.\n\n   int oppFloatIndex =\n      listOpp->findLastBeforeSideSpanningIndex (vloat->sideSpanningIndex);\n   // Generally, the rules are simple: loop as long as the vertical\n   // dimensions test is positive (and, of course, there are floats),\n   // ...\n   for (bool foundColl = false;\n        !foundColl && oppFloatIndex >= 0 &&\n           collidesV (vloat, listOpp->get (oppFloatIndex), &yRealNew);\n        oppFloatIndex--) {\n      // ... but stop the loop as soon as the horizontal dimensions\n      // test is positive.\n      if (collidesH (vloat, listOpp->get (oppFloatIndex))) {\n         vloat->yReal = yRealNew;\n         DBG_OBJ_SET_NUM_O (vloat->getWidget (), \"<Float>.yReal\", vloat->yReal);\n         foundColl = true;\n      }\n   }\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"vloat->yReq = %d, vloat->yReal = %d\",\n                 vloat->yReq, vloat->yReal);\n\n   // In some cases, an explicit update is neccessary, as in this example:\n   //\n   // <body>\n   //     <div id=\"a\">\n   //         <div id=\"b\" style=\"float:left\">main</div>\n   //     </div>\n   //     <div id=\"c\" style=\"clear:both\">x</div>\n   //     <div id=\"d\">footer</div>\n   // </body>\n   //\n   // Without an explicit update, #c would keep an old value for extraSpace.top,\n   // based on the old value of vloat->yReal.\n   //\n   // Notice that #c would be updated otherwise, if it had at least one word\n   // content.\n\n   if (vloat->yReal != oldYReal)\n      updateGenerators (vloat);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFFloatsMgr::tellPosition2 (Widget *widget, int x, int y)\n{\n   // Nothing to do.\n}\n\nvoid OOFFloatsMgr::tellIncompletePosition1 (Widget *generator, Widget *widget,\n                                            int x, int y)\n{\n   notImplemented (\"OOFFloatsMgr::tellIncompletePosition1\");\n}\n\nvoid OOFFloatsMgr::tellIncompletePosition2 (Widget *generator, Widget *widget,\n                                            int x, int y)\n{\n   notImplemented (\"OOFFloatsMgr::tellIncompletePosition2\");\n}\n\nbool OOFFloatsMgr::collidesV (Float *vloat, Float *other, int *yReal)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"collidesV\", \"#%d [%p], #%d [%p], ...\",\n                  vloat->index, vloat->getWidget (), other->index,\n                  other->getWidget ());\n\n   bool result;\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"initial yReal = %d\", vloat->yReal);\n\n   ensureFloatSize (other);\n   int otherBottomGB = other->yReal + other->size.ascent + other->size.descent;\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"otherBottomGB = %d + (%d + %d) = %d\",\n                 other->yReal, other->size.ascent, other->size.descent,\n                 otherBottomGB);\n\n   if (vloat->yReal <  otherBottomGB) {\n      *yReal = otherBottomGB;\n      result = true;\n   } else\n      result = false;\n\n   if (result)\n      DBG_OBJ_LEAVE_VAL (\"%s, %d\", \"true\", *yReal);\n   else\n      DBG_OBJ_LEAVE_VAL (\"%s\", \"false\");\n\n   return result;\n}\n\n\nbool OOFFloatsMgr::collidesH (Float *vloat, Float *other)\n{\n   // Only checks horizontal collision. For a complete test, use collidesV (...)\n   // && collidesH (...).\n   bool collidesH;\n\n   int vloatX = calcFloatX (vloat), otherX = calcFloatX (other);\n\n   // Generally: right border of the left float > left border of the right float\n   // (all in canvas coordinates).\n   if (vloat->getWidget()->getStyle()->vloat == FLOAT_LEFT) {\n      // \"vloat\" is left, \"other\" is right\n      ensureFloatSize (vloat);\n      collidesH = vloatX + vloat->size.width > otherX;\n   } else {\n      // \"other\" is left, \"vloat\" is right\n      ensureFloatSize (other);\n      collidesH = otherX + other->size.width > vloatX;\n   }\n\n   return collidesH;\n}\n\nvoid OOFFloatsMgr::getFloatsListsAndSide (Float *vloat,\n                                          SortedFloatsVector **listSame,\n                                          SortedFloatsVector **listOpp,\n                                          Side *side)\n{\n   switch (vloat->getWidget()->getStyle()->vloat) {\n   case FLOAT_LEFT:\n      if (listSame) *listSame = leftFloats;\n      if (listOpp) *listOpp = rightFloats;\n      if (side) *side = LEFT;\n      break;\n\n   case FLOAT_RIGHT:\n      if (listSame) *listSame = rightFloats;\n      if (listOpp) *listOpp = leftFloats;\n      if (side) *side = RIGHT;\n      break;\n\n   default:\n      assertNotReached();\n   }\n}\n\nvoid OOFFloatsMgr::getSize (Requisition *cbReq, int *oofWidth, int *oofHeight)\n{\n   DBG_OBJ_ENTER0 (\"resize.oofm\", 0, \"getSize\");\n\n   int oofWidthtLeft, oofWidthRight, oofHeightLeft, oofHeightRight;\n   getFloatsSize (cbReq, LEFT, &oofWidthtLeft, &oofHeightLeft);\n   getFloatsSize (cbReq, RIGHT, &oofWidthRight, &oofHeightRight);\n\n   // Floats must be within the *content* area of the containing\n   // block, not its *margin* area (which is equivalent to the\n   // requisition / allocation). For this reason, boxRestWidth() and\n   // boxRestHeight() are added here.\n\n   *oofWidth =\n      max (oofWidthtLeft, oofWidthRight) + container->boxRestWidth ();\n   *oofHeight =\n      max (oofHeightLeft, oofHeightRight) + container->boxRestHeight ();\n\n   SizeChanged = true;\n   DBG_OBJ_SET_NUM (\"floatRef\", floatRef);\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1,\n                 \"=> (l: %d, r: %d => %d) * (l: %d, r: %d => %d)\",\n                 oofWidthtLeft, oofWidthRight, *oofWidth, oofHeightLeft,\n                 oofHeightRight, *oofHeight);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFFloatsMgr::getFloatsSize (Requisition *cbReq, Side side, int *width,\n                                  int *height)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getFloatsSize\", \"(%d * (%d + %d), %s, ...\",\n                  cbReq->width, cbReq->ascent, cbReq->descent,\n                  side == LEFT ? \"LEFT\" : \"RIGHT\");\n\n   SortedFloatsVector *list = side == LEFT ? leftFloats : rightFloats;\n\n   *width = *height = 0;\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"%d floats on this side\", list->size());\n\n   for (int i = 0; i < list->size(); i++) {\n      Float *vloat = list->get(i);\n\n      DBG_OBJ_MSGF (\"resize.oofm\", 1,\n                    \"float %p has generator %p (container is %p)\",\n                    vloat->getWidget (), vloat->generator, container);\n                    \n      ensureFloatSize (vloat);\n      \n      *width = max (*width, calcFloatX (vloat) + vloat->size.width);\n      *height = max (*height,\n                     vloat->yReal + vloat->size.ascent + vloat->size.descent);\n   }\n   \n   DBG_OBJ_LEAVE ();\n}\n\nbool OOFFloatsMgr::containerMustAdjustExtraSpace ()\n{\n   return false;\n}\n\nvoid OOFFloatsMgr::getExtremes (Extremes *cbExtr, int *oofMinWidth,\n                                int *oofMaxWidth)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getExtremes\", \"(%d / %d), ...\",\n                  cbExtr->minWidth, cbExtr->maxWidth);\n\n   int oofMinWidthtLeft, oofMinWidthRight, oofMaxWidthLeft, oofMaxWidthRight;\n   getFloatsExtremes (cbExtr, LEFT, &oofMinWidthtLeft, &oofMaxWidthLeft);\n   getFloatsExtremes (cbExtr, RIGHT, &oofMinWidthRight, &oofMaxWidthRight);\n\n   *oofMinWidth = max (oofMinWidthtLeft, oofMinWidthRight);\n   *oofMaxWidth = max (oofMaxWidthLeft, oofMaxWidthRight);\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1,\n                 \"=> (l: %d, r: %d => %d) / (l: %d, r: %d => %d)\",\n                 oofMinWidthtLeft, oofMinWidthRight, *oofMinWidth,\n                 oofMaxWidthLeft, oofMaxWidthRight, *oofMaxWidth);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFFloatsMgr::getFloatsExtremes (Extremes *cbExtr, Side side,\n                                      int *minWidth, int *maxWidth)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getFloatsExtremes\", \"(%d / %d), %s, ...\",\n                  cbExtr->minWidth, cbExtr->maxWidth,\n                  side == LEFT ? \"LEFT\" : \"RIGHT\");\n\n   *minWidth = *maxWidth = 0;\n\n   SortedFloatsVector *list = side == LEFT ? leftFloats : rightFloats;\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"%d floats to be examined\", list->size());\n\n   for (int i = 0; i < list->size(); i++) {\n      Float *vloat = list->get(i);\n\n      DBG_OBJ_MSGF (\"resize.oofm\", 1,\n                    \"float %p has generator %p (container is %p)\",\n                    vloat->getWidget (), vloat->generator,\n                    container);\n      \n      Extremes extr;\n      vloat->getWidget()->getExtremes (&extr);\n      \n      // The calculation of extremes must be kept consistent with\n      // getFloatsSize(). Especially this means for the *minimal* width:\n      //\n      // - The right border (difference between float and container) does not\n      //   have to be considered (see getFloatsSize()).\n      //\n      // - This is also the case for the left border, as seen in calcFloatX()\n      //   (\"... but when the float exceeds the line break width\" ...).\n      \n      *minWidth = max (*minWidth, extr.minWidth);\n      \n      // For the maximal width, borders must be considered.\n      *maxWidth = max (*maxWidth,\n                       extr.maxWidth\n                       + vloat->generator->getStyle()->boxDiffWidth(),\n                       + max (container->getGeneratorWidth ()\n                              - vloat->generator->getGeneratorWidth (),\n                              0));\n      \n      DBG_OBJ_MSGF (\"resize.oofm\", 1, \"%d / %d => %d / %d\",\n                    extr.minWidth, extr.maxWidth, *minWidth, *maxWidth);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nOOFFloatsMgr::TBInfo *OOFFloatsMgr::getOOFAwareWidgetWhenRegistered\n   (OOFAwareWidget *widget)\n{\n   DBG_OBJ_ENTER (\"oofm.common\", 0, \"getOOFAwareWidgetWhenRegistered\", \"%p\",\n                  widget);\n   TypedPointer<OOFAwareWidget> key (widget);\n   TBInfo *tbInfo = tbInfosByOOFAwareWidget->get (&key);\n   DBG_OBJ_MSGF (\"oofm.common\", 1, \"found? %s\", tbInfo ? \"yes\" : \"no\");\n   DBG_OBJ_LEAVE ();\n   return tbInfo;\n}\n\nOOFFloatsMgr::TBInfo *OOFFloatsMgr::getOOFAwareWidget (OOFAwareWidget *widget)\n{\n   DBG_OBJ_ENTER (\"oofm.common\", 0, \"getOOFAwareWidget\", \"%p\", widget);\n   TBInfo *tbInfo = getOOFAwareWidgetWhenRegistered (widget);\n   assert (tbInfo);\n   DBG_OBJ_LEAVE ();\n   return tbInfo;\n}\n\nint OOFFloatsMgr::getLeftBorder (int y, int h, OOFAwareWidget *lastGB,\n                                 int lastExtIndex)\n{\n   int b = getBorder (LEFT, y, h, lastGB, lastExtIndex);\n   DBG_OBJ_MSGF (\"border\", 0, \"left border (%d, %d, %p, %d) => %d\",\n                 y, h, lastGB, lastExtIndex, b);\n   return b;\n}\n\nint OOFFloatsMgr::getRightBorder (int y, int h, OOFAwareWidget *lastGB,\n                                  int lastExtIndex)\n{\n   int b = getBorder (RIGHT, y, h, lastGB, lastExtIndex);\n   DBG_OBJ_MSGF (\"border\", 0, \"right border (%d, %d, %p, %d) => %d\",\n                 y, h, lastGB, lastExtIndex, b);\n   return b;\n}\n\nint OOFFloatsMgr::getBorder (Side side, int y, int h, OOFAwareWidget *lastGB,\n                             int lastExtIndex)\n{\n   DBG_OBJ_ENTER (\"border\", 0, \"getBorder\", \"%s, %d, %d, %p, %d\",\n                  side == LEFT ? \"LEFT\" : \"RIGHT\", y, h, lastGB, lastExtIndex);\n\n   SortedFloatsVector *list = side == LEFT ? leftFloats : rightFloats;\n   int last;   \n   int first = list->findFirst (y, h, lastGB, lastExtIndex, &last);\n   int border = 0;\n\n   DBG_OBJ_MSGF (\"border\", 1, \"first = %d\", first);\n\n   if (first != -1) {\n      // It is not sufficient to find the first float, since a line\n      // (with height h) may cover the region of multiple float, of\n      // which the widest has to be choosen.\n      bool covers = true;\n\n      // We are not searching until the end of the list, but until the\n      // float defined by lastGB and lastExtIndex.\n      for (int i = first; covers && i <= last; i++) {\n         Float *vloat = list->get(i);\n         covers = vloat->covers (y, h);\n         DBG_OBJ_MSGF (\"border\", 1, \"float %d (%p) covers? %s.\",\n                       i, vloat->getWidget(), covers ? \"<b>yes</b>\" : \"no\");\n\n         if (covers) {\n            int d;\n            switch (side) {\n            case LEFT:\n               d = vloat->generator->getGeneratorX (oofmIndex)\n                  + vloat->generator->getStyle()->boxOffsetX ();\n               break;\n\n            case RIGHT:\n               // There is no simple possibility to get the difference between\n               // container and generator at the right border (as it is at the\n               // left border, see above). We have to calculate the difference\n               // between the maximal widths.\n               d = container->getMaxGeneratorWidth ()\n                  - (vloat->generator->getGeneratorX (oofmIndex)\n                     + vloat->generator->getMaxGeneratorWidth ())\n                  + vloat->generator->getStyle()->boxRestWidth ();\n               break;\n\n            default:\n               assertNotReached ();\n               d = 0;\n               break;\n            }\n\n            int thisBorder = vloat->size.width + d;\n            DBG_OBJ_MSGF (\"border\", 1, \"thisBorder = %d + %d = %d\",\n                          vloat->size.width, d, thisBorder);\n            \n            border = max (border, thisBorder);\n         }\n      }\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", border);\n   return border;\n}\n\nbool OOFFloatsMgr::hasFloatLeft (int y, int h, OOFAwareWidget *lastGB,\n                                 int lastExtIndex)\n{\n   bool b = hasFloat (LEFT, y, h, lastGB, lastExtIndex);\n   DBG_OBJ_MSGF (\"border\", 0, \"has float left (%d, %d, %p, %d) => %s\",\n                 y, h, lastGB, lastExtIndex, b ? \"true\" : \"false\");\n   return b;\n}\n\nbool OOFFloatsMgr::hasFloatRight (int y, int h, OOFAwareWidget *lastGB,\n                                  int lastExtIndex)\n{\n   bool b = hasFloat (RIGHT, y, h, lastGB, lastExtIndex);\n   DBG_OBJ_MSGF (\"border\", 0, \"has float right (%d, %d, %p, %d) => %s\",\n                 y, h, lastGB, lastExtIndex, b ? \"true\" : \"false\");\n   return b;\n}\n\nbool OOFFloatsMgr::hasFloat (Side side, int y, int h, OOFAwareWidget *lastGB,\n                             int lastExtIndex)\n{\n   DBG_OBJ_ENTER (\"border\", 0, \"hasFloat\", \"%s, %d, %d, %p, %d\",\n                  side == LEFT ? \"LEFT\" : \"RIGHT\", y, h, lastGB, lastExtIndex);\n\n   SortedFloatsVector *list = side == LEFT ? leftFloats : rightFloats;\n   int first = list->findFirst (y, h, lastGB, lastExtIndex, NULL);\n\n   DBG_OBJ_MSGF (\"border\", 1, \"first = %d\", first);\n   DBG_OBJ_LEAVE ();\n   return first != -1;\n}\n\nint OOFFloatsMgr::getLeftFloatHeight (int y, int h, OOFAwareWidget *lastGB,\n                                      int lastExtIndex)\n{\n   return getFloatHeight (LEFT, y, h, lastGB, lastExtIndex);\n}\n\nint OOFFloatsMgr::getRightFloatHeight (int y, int h, OOFAwareWidget *lastGB,\n                                       int lastExtIndex)\n{\n   return getFloatHeight (RIGHT, y, h, lastGB, lastExtIndex);\n}\n\nint OOFFloatsMgr::getFloatHeight (Side side, int y, int h,\n                                  OOFAwareWidget *lastGB, int lastExtIndex)\n{\n   DBG_OBJ_ENTER (\"border\", 0, \"getFloatHeight\", \"%s, %d, %d, %p, %d\",\n                  side == LEFT ? \"LEFT\" : \"RIGHT\", y, h, lastGB, lastExtIndex);\n\n   SortedFloatsVector *list = side == LEFT ? leftFloats : rightFloats;\n   int first = list->findFirst (y, h, lastGB, lastExtIndex, NULL);\n   assert (first != -1);   /* This method must not be called when there is no\n                              float on the respective side. */\n\n   Float *vloat = list->get (first);\n   int yRelToFloat = y - vloat->yReal;\n   DBG_OBJ_MSGF (\"border\", 1, \"caller is CB: yRelToFloat = %d - %d = %d\",\n                 y, vloat->yReal, yRelToFloat);\n\n   ensureFloatSize (vloat);\n   int height = vloat->size.ascent + vloat->size.descent - yRelToFloat;\n\n   DBG_OBJ_MSGF (\"border\", 1, \"=> (%d + %d) - %d = %d\",\n                 vloat->size.ascent, vloat->size.descent, yRelToFloat, height);\n   DBG_OBJ_LEAVE ();\n   return height;\n}\n\nint OOFFloatsMgr::getClearPosition (OOFAwareWidget *widget)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getClearPosition\", \"%p\", widget);\n\n   int pos;\n\n   if (widget->getStyle()) {\n      bool left = false, right = false;\n      switch (widget->getStyle()->clear) {\n      case CLEAR_NONE: break;\n      case CLEAR_LEFT: left = true; break;\n      case CLEAR_RIGHT: right = true; break;\n      case CLEAR_BOTH: left = right = true; break;\n      default: assertNotReached ();\n      }\n\n      pos = max (left ? getClearPosition (widget, LEFT) : 0,\n                 right ? getClearPosition (widget, RIGHT) : 0);\n   } else\n      pos = 0;\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", pos);\n   return pos;\n}\n\nbool OOFFloatsMgr::affectsLeftBorder (core::Widget *widget)\n{\n   return widget->getStyle()->vloat == core::style::FLOAT_LEFT;\n}\n\nbool OOFFloatsMgr::affectsRightBorder (core::Widget *widget)\n{\n   return widget->getStyle()->vloat == core::style::FLOAT_RIGHT;\n};\n\nbool OOFFloatsMgr::mayAffectBordersAtAll ()\n{\n   return true;\n}\n\nint OOFFloatsMgr::getClearPosition (OOFAwareWidget *widget, Side side)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getClearPosition\", \"%p, %s\",\n                  widget, side == LEFT ? \"LEFT\" : \"RIGHT\");\n\n   int pos;\n   SortedFloatsVector *list = side == LEFT ? leftFloats : rightFloats;\n\n   // Search the last float before (therfore -1) this widget.\n   int i = list->findFloatIndex (widget, -1);\n   if (i < 0)\n      pos = 0;\n   else {\n      Float *vloat = list->get(i);\n      assert (vloat->generator != widget);\n      ensureFloatSize (vloat);\n      int yRel = widget->getGeneratorY (oofmIndex);\n      pos = max (vloat->yReal + vloat->size.ascent + vloat->size.descent - yRel,\n                 0);\n      DBG_OBJ_MSGF (\"resize.oofm\", 1, \"pos = max (%d + %d + %d - %d, 0)\",\n                    vloat->yReal, vloat->size.ascent, vloat->size.descent,\n                    yRel);\n   }\n   \n   DBG_OBJ_LEAVE_VAL (\"%d\", pos);\n\n   return pos;\n}\n\nvoid OOFFloatsMgr::ensureFloatSize (Float *vloat)\n{\n   // Historical note: relative sizes (e. g. percentages) are already\n   // handled by (at this time) Layout::containerSizeChanged, so\n   // Float::dirty will be set.\n\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"ensureFloatSize\", \"%p\",\n                  vloat->getWidget ());\n\n   if (vloat->dirty)  {\n      DBG_OBJ_MSG (\"resize.oofm\", 1, \"dirty: recalculation\");\n\n      vloat->getWidget()->sizeRequest (&vloat->size);\n      vloat->dirty = false;\n      DBG_OBJ_SET_BOOL_O (vloat->getWidget (), \"<Float>.dirty\", vloat->dirty);\n\n      DBG_OBJ_MSGF (\"resize.oofm\", 1, \"size: %d * (%d + %d)\",\n                    vloat->size.width, vloat->size.ascent, vloat->size.descent);\n\n      DBG_OBJ_SET_NUM_O (vloat->getWidget(), \"<Float>.size.width\",\n                         vloat->size.width);\n      DBG_OBJ_SET_NUM_O (vloat->getWidget(), \"<Float>.size.ascent\",\n                         vloat->size.ascent);\n      DBG_OBJ_SET_NUM_O (vloat->getWidget(), \"<Float>.size.descent\",\n                         vloat->size.descent);\n\n      // \"sizeChangedSinceLastAllocation\" is reset in sizeAllocateEnd()\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nbool OOFFloatsMgr::dealingWithSizeOfChild (core::Widget *child)\n{\n   return false;\n}\n\nint OOFFloatsMgr::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   notImplemented (\"OOFFloatsMgr::getAvailWidthOfChild\");\n   return 0;\n}\n\nint OOFFloatsMgr::getAvailHeightOfChild (Widget *child, bool forceValue)\n{\n   notImplemented (\"OOFFloatsMgr::getAvailHeightOfChild\");\n   return 0;\n}\n\nint OOFFloatsMgr::getNumWidgets ()\n{\n   return leftFloats->size() + rightFloats->size();\n}\n\nWidget *OOFFloatsMgr::getWidget (int i)\n{\n   if (i < leftFloats->size())\n      return leftFloats->get(i)->getWidget ();\n   else\n      return rightFloats->get(i - leftFloats->size())->getWidget ();\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/ooffloatsmgr.hh",
    "content": "#ifndef __DW_OOFFLOATSMGR_HH__\n#define __DW_OOFFLOATSMGR_HH__\n\n#include \"outofflowmgr.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\n/**\n * \\brief OutOfFlowMgr implementation dealing with floats.\n *\n * Note: The identifiers and comments of this class still refer to\n * \"Textblock\" instead of \"OOFAwareWidget\"; should be cleaned up some\n * day. (OTOH, these widgets are always textblocks.)\n */\nclass OOFFloatsMgr: public OutOfFlowMgr\n{\n   friend class WidgetInfo;\n\nprivate:\n   enum Side { LEFT, RIGHT };\n\n   OOFAwareWidget *container;\n   int oofmIndex;\n\n   core::Allocation containerAllocation;\n\n   class WidgetInfo: public lout::object::Object\n   {\n   private:\n      OOFFloatsMgr *oofm;\n      core::Widget *widget;\n\n   protected:\n      OOFFloatsMgr *getOOFFloatsMgr () { return oofm; }\n\n   public:\n      WidgetInfo (OOFFloatsMgr *oofm, core::Widget *widget);\n\n      inline core::Widget *getWidget () { return widget; }\n   };\n\n   class Float: public WidgetInfo\n   {\n   public:\n      class ComparePosition: public lout::object::Comparator\n      {\n      public:\n         int compare (Object *o1, Object *o2);\n      };\n\n      class CompareSideSpanningIndex: public lout::object::Comparator\n      {\n      public:\n         int compare (Object *o1, Object *o2);\n      };\n\n      class CompareGBAndExtIndex: public lout::object::Comparator\n      {\n      private:\n         OOFFloatsMgr *oofm;\n\n      public:\n         CompareGBAndExtIndex (OOFFloatsMgr *oofm)\n         { this->oofm = oofm; }\n         int compare(Object *o1, Object *o2);\n      };\n\n      OOFAwareWidget *generator;\n      int externalIndex;\n      int index; // TODO Needed after SRDOP?\n      int yReq, yReal; // relative to container\n      int sideSpanningIndex;\n      core::Requisition size;\n      bool dirty;\n\n      Float (OOFFloatsMgr *oofm, core::Widget *widget,\n             OOFAwareWidget *generatingBlock, int externalIndex);\n\n      void intoStringBuffer(lout::misc::StringBuffer *sb);\n\n      bool covers (int y, int h);\n   };\n\n   /**\n    * This list is kept sorted.\n    *\n    * To prevent accessing methods of the base class in an\n    * uncontrolled way, the inheritance is private, not public; this\n    * means that all methods must be delegated (see iterator(), size()\n    * etc. below.)\n    *\n    * TODO Update comment: still sorted, but ...\n    *\n    * More: add() and change() may check order again.\n    */\n   class SortedFloatsVector: private lout::container::typed::Vector<Float>\n   {\n   private:\n      OOFFloatsMgr *oofm;\n      Side side;\n\n   public:\n      inline SortedFloatsVector (OOFFloatsMgr *oofm, Side side,\n                                 bool ownerOfObjects) :\n         lout::container::typed::Vector<Float> (1, ownerOfObjects)\n      { this->oofm = oofm; this->side = side; }\n\n      int findFloatIndex (OOFAwareWidget *lastGB, int lastExtIndex);\n      int find (int y, int start, int end);\n      int findFirst (int y, int h, OOFAwareWidget *lastGB, int lastExtIndex,\n                     int *lastReturn);\n      int findLastBeforeSideSpanningIndex (int sideSpanningIndex);\n      void put (Float *vloat);\n\n      inline lout::container::typed::Iterator<Float> iterator()\n      { return lout::container::typed::Vector<Float>::iterator (); }\n      inline int size ()\n      { return lout::container::typed::Vector<Float>::size (); }\n      inline Float *get (int pos)\n      { return lout::container::typed::Vector<Float>::get (pos); }\n      inline void clear ()\n      { lout::container::typed::Vector<Float>::clear (); }\n   };\n\n   class TBInfo: public WidgetInfo\n   {\n   public:\n      class ComparePosition: public lout::object::Comparator\n      {\n      private:\n         int oofmIndex;\n      \n      public:\n         inline ComparePosition (int oofmIndex) { this->oofmIndex = oofmIndex; }\n         int compare (Object *o1, Object *o2);\n      };\n\n      int index; // position within \"tbInfos\"\n      int y;     // used for sorting\n\n      TBInfo *parent;\n      int parentExtIndex;\n\n      // These two lists store all floats of a generator, in the order\n      // in which they are defined. Used for optimization\n      lout::container::typed::Vector<Float> *leftFloats, *rightFloats;\n\n      TBInfo (OOFFloatsMgr *oofm, OOFAwareWidget *textblock,\n              TBInfo *parent, int parentExtIndex);\n      ~TBInfo ();\n\n      inline OOFAwareWidget *getOOFAwareWidget ()\n      { return (OOFAwareWidget*)getWidget (); }\n   };\n\n   SortedFloatsVector *leftFloats, *rightFloats;\n\n   lout::container::typed::HashTable<lout::object::TypedPointer\n                                     <dw::core::Widget>, Float> *floatsByWidget;\n\n   lout::container::typed::Vector<TBInfo> *tbInfos;\n   lout::container::typed::HashTable<lout::object::TypedPointer<OOFAwareWidget>,\n                                     TBInfo> *tbInfosByOOFAwareWidget;\n\n   int lastLeftTBIndex, lastRightTBIndex, leftFloatsMark, rightFloatsMark;\n   bool SizeChanged;\n\n   void moveExternalIndices (lout::container::typed::Vector<Float> *list,\n                             int oldStartIndex, int diff);\n   Float *findFloatByWidget (core::Widget *widget);\n   void updateGenerators (Float *vloat);\n   int findTBInfo (int y);\n\n   void sizeAllocateFloats (Side side);\n   int getGBWidthForAllocation (Float *vloat);\n   int calcFloatX (Float *vloat);\n\n   void drawFloats (SortedFloatsVector *list, core::View *view,\n                    core::Rectangle *area, core::DrawingContext *context);\n   core::Widget *getFloatWidgetAtPoint (SortedFloatsVector *list, int x, int y,\n                                        core::GettingWidgetAtPointContext\n                                        *context);\n\n   bool collidesV (Float *vloat, Float *other, int *yReal);\n   bool collidesH (Float *vloat, Float *other);\n\n   void getFloatsListsAndSide (Float *vloat, SortedFloatsVector **listSame,\n                               SortedFloatsVector **listOpp, Side *side);\n\n   void getFloatsSize (core::Requisition *cbReq, Side side, int *width,\n                       int *height);\n   void getFloatsExtremes (core::Extremes *cbExtr, Side side, int *minWidth,\n                           int *maxWidth);\n\n   TBInfo *getOOFAwareWidget (OOFAwareWidget *widget);\n   TBInfo *getOOFAwareWidgetWhenRegistered (OOFAwareWidget *widget);\n   inline bool isOOFAwareWidgetRegistered (OOFAwareWidget *widget)\n   { return getOOFAwareWidgetWhenRegistered (widget) != NULL; }\n\n   int getBorder (Side side, int y, int h, OOFAwareWidget *lastGB,\n                  int lastExtIndex);\n   bool hasFloat (Side side, int y, int h, OOFAwareWidget *lastGB,\n                  int lastExtIndex);\n   int getFloatHeight (Side side, int y, int h, OOFAwareWidget *lastGB,\n                       int lastExtIndex);\n\n   int getClearPosition (OOFAwareWidget *widget, Side side);\n\n   void ensureFloatSize (Float *vloat);\n\n   inline static int createSubRefLeftFloat (int index) { return index << 1; }\n   inline static int createSubRefRightFloat (int index)\n   { return (index << 1) | 1; }\n\n   inline static bool isSubRefLeftFloat (int ref)\n   { return ref != -1 && (ref & 1) == 0; }\n   inline static bool isSubRefRightFloat (int ref)\n   { return ref != -1 && (ref & 1) == 1; }\n\n   inline static int getFloatIndexFromSubRef (int ref)\n   { return ref == -1 ? ref : (ref >> 1); }\n\npublic:\n   OOFFloatsMgr (OOFAwareWidget *container, int oofmIndex);\n   ~OOFFloatsMgr ();\n\n   void sizeAllocateStart (OOFAwareWidget *caller,\n                           core::Allocation *allocation);\n   void sizeAllocateEnd (OOFAwareWidget *caller);\n   void containerSizeChangedForChildren ();\n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n\n   void markSizeChange (int ref);\n   void markExtremesChange (int ref);\n   core::Widget *getWidgetAtPoint (int x, int y,\n                                   core::GettingWidgetAtPointContext *context);\n\n   void addWidgetInFlow (OOFAwareWidget *textblock, OOFAwareWidget *parentBlock,\n                         int externalIndex);\n   int addWidgetOOF (core::Widget *widget, OOFAwareWidget *generatingBlock,\n                     int externalIndex);\n   void calcWidgetRefSize (core::Widget *widget,core::Requisition *size);\n   void moveExternalIndices (OOFAwareWidget *generatingBlock, int oldStartIndex,\n                             int diff);\n\n   void tellPosition1 (core::Widget *widget, int x, int y);\n   void tellPosition2 (core::Widget *widget, int x, int y);\n   void tellIncompletePosition1 (core::Widget *generator, core::Widget *widget,\n                                 int x, int y);\n   void tellIncompletePosition2 (core::Widget *generator, core::Widget *widget,\n                                 int x, int y);\n\n   void getSize (core::Requisition *cbReq, int *oofWidth, int *oofHeight);\n   bool containerMustAdjustExtraSpace ();\n   void getExtremes (core::Extremes *cbExtr,\n                     int *oofMinWidth, int *oofMaxWidth);\n\n   int getLeftBorder (int y, int h, OOFAwareWidget *lastGB, int lastExtIndex);\n   int getRightBorder (int y, int h, OOFAwareWidget *lastGB, int lastExtIndex);\n\n   bool hasFloatLeft (int y, int h, OOFAwareWidget *lastGB, int lastExtIndex);\n   bool hasFloatRight (int y, int h, OOFAwareWidget *lastGB, int lastExtIndex);\n\n   int getLeftFloatHeight (int y, int h, OOFAwareWidget *lastGB,\n                           int lastExtIndex);\n   int getRightFloatHeight (int y, int h, OOFAwareWidget *lastGB,\n                            int lastExtIndex);\n\n   bool affectsLeftBorder (core::Widget *widget);\n   bool affectsRightBorder (core::Widget *widget);\n   bool mayAffectBordersAtAll ();\n\n   int getClearPosition (OOFAwareWidget *textblock);\n\n   bool dealingWithSizeOfChild (core::Widget *child);\n   int getAvailWidthOfChild (core::Widget *child, bool forceValue);\n   int getAvailHeightOfChild (core::Widget *child, bool forceValue);\n\n   int getNumWidgets ();\n   core::Widget *getWidget (int i);\n};\n\n} // namespace oof\n\n} // namespace dw\n\n#endif // __DW_OOFFLOATSMGR_HH__\n"
  },
  {
    "path": "dw/oofposabslikemgr.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2015 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"oofposabslikemgr.hh\"\n\nusing namespace dw::core;\nusing namespace lout::misc;\n\nnamespace dw {\n\nnamespace oof {\n\nOOFPosAbsLikeMgr::OOFPosAbsLikeMgr (OOFAwareWidget *container) :\n   OOFPositionedMgr (container)\n{\n   DBG_OBJ_CREATE (\"dw::oof::OOFPosAbsLikeMgr\");\n}\n\nOOFPosAbsLikeMgr::~OOFPosAbsLikeMgr ()\n{\n   DBG_OBJ_DELETE ();\n}\n\nvoid OOFPosAbsLikeMgr::calcWidgetRefSize (Widget *widget, Requisition *size)\n{\n   size->width = size->ascent = size->descent = 0;\n}\n\nvoid OOFPosAbsLikeMgr::sizeAllocateChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize.oofm\", 0, \"sizeAllocateChildren\");\n\n   int refWidth = container->getAvailWidth (true) - containerBoxDiffWidth ();\n   int refHeight = container->getAvailHeight (true) - containerBoxDiffHeight ();\n\n   for (int i = 0; i < children->size(); i++) {\n      Child *child = children->get (i);\n\n      int x, y, width, ascent, descent;\n      calcPosAndSizeChildOfChild (child, refWidth, refHeight, &x, &y, &width,\n                                  &ascent, &descent);\n\n      Allocation childAllocation;\n      childAllocation.x = containerAllocation.x + x + containerBoxOffsetX ();\n      childAllocation.y = containerAllocation.y + y + containerBoxOffsetY ();\n      childAllocation.width = width;\n      childAllocation.ascent = ascent;\n      childAllocation.descent = descent;\n\n      child->widget->sizeAllocate (&childAllocation);\n   }\n   \n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFPosAbsLikeMgr::getSize (Requisition *containerReq, int *oofWidth,\n                                int *oofHeight)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getSize\", \"%d * (%d + %d)\",\n                  containerReq->width, containerReq->ascent,\n                  containerReq->descent);\n\n   *oofWidth = *oofHeight = 0;\n\n   int refWidth = container->getAvailWidth (true);\n   int refHeight = container->getAvailHeight (true);\n\n   for (int i = 0; i < children->size(); i++) {\n      Child *child = children->get(i);\n\n      // Children whose position cannot be determined will be\n      // considered later in sizeAllocateEnd.\n      if (posXDefined (child) && posYDefined (child)) {\n         int x, y, width, ascent, descent;\n         calcPosAndSizeChildOfChild (child, refWidth, refHeight, &x, &y, &width,\n                                     &ascent, &descent);\n         *oofWidth = max (*oofWidth, x + width) + containerBoxDiffWidth ();\n         *oofHeight =\n            max (*oofHeight, y + ascent + descent) + containerBoxDiffHeight ();\n\n         child->consideredForSize = true;\n      } else\n         child->consideredForSize = false;\n   }      \n\n   DBG_OBJ_LEAVE_VAL (\"%d * %d\", *oofWidth, *oofHeight);\n}\n\nvoid OOFPosAbsLikeMgr::getExtremes (Extremes *containerExtr, int *oofMinWidth,\n                                    int *oofMaxWidth)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getExtremes\", \"(%d / %d), ...\",\n                  containerExtr->minWidth, containerExtr->maxWidth);\n\n   *oofMinWidth = *oofMaxWidth = 0;\n\n   for (int i = 0; i < children->size(); i++) {\n      Child *child = children->get(i);\n\n      // Children whose position cannot be determined will be\n      // considered later in sizeAllocateEnd.\n      if (posXDefined (child)) {\n         int x, width;\n         Extremes childExtr;\n         child->widget->getExtremes (&childExtr);\n         \n         // Here, we put the extremes of the container in relation to\n         // the extremes of the child, as sizes are put in relation\n         // for calculating the size. In one case, the allocation is\n         // used: when neither \"left\" nor \"right\" is set, and so the\n         // position told by the generator is used.\n         //\n         // If you look at the Textblock widget, you'll find that this\n         // is always boxOffsetX(), and the horizontal position of a\n         // textblock within its parent is also constant; so this is\n         // not a problem.\n         //\n         // (TODO What about a table cell within a table?)\n\n         calcHPosAndSizeChildOfChild (child, containerExtr->minWidth,\n                                      childExtr.minWidth, &x, &width);\n         *oofMinWidth = max (*oofMinWidth, x + width);\n\n         calcHPosAndSizeChildOfChild (child, containerExtr->maxWidth,\n                                      childExtr.maxWidth, &x, &width);\n         *oofMaxWidth = max (*oofMaxWidth, x + width);\n\n         child->consideredForExtremes = true;\n      } else\n         child->consideredForExtremes = false;\n   }      \n\n   *oofMinWidth += containerBoxDiffWidth ();\n   *oofMaxWidth += containerBoxDiffWidth ();\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 0, \"=> %d / %d\", *oofMinWidth, *oofMaxWidth);\n   DBG_OBJ_LEAVE ();\n}\n\nint OOFPosAbsLikeMgr::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0,\n                  \"OOFPositionedMgr/getAvailWidthOfChild\", \"%p, %s\",\n                  child, forceValue ? \"true\" : \"false\");\n\n   int width;\n\n   if (child->getStyle()->width == style::LENGTH_AUTO &&\n       child->getStyle()->minWidth == style::LENGTH_AUTO &&\n       child->getStyle()->maxWidth == style::LENGTH_AUTO) {\n      // TODO This should (perhaps?) only used when 'width' is undefined.\n      // TODO Is \"boxDiffWidth()\" correct here?\n      DBG_OBJ_MSG (\"resize.oofm\", 1, \"no specification\");\n      if (forceValue) {\n         int availWidth = container->getAvailWidth (true), left, right;\n\n         // Regard undefined values as 0:\n         if (!getPosLeft (child, availWidth, &left)) left = 0;\n         if (!getPosRight (child, availWidth, &right)) right = 0;\n\n         width = max (availWidth - containerBoxDiffWidth () - left - right, 0);\n      } else\n         width = -1;\n   } else {\n      if (forceValue) {\n         int availWidth = container->getAvailWidth (true);\n         child->calcFinalWidth (child->getStyle(),\n                                availWidth - containerBoxDiffWidth (), NULL,\n                                0, true, &width);\n      } else\n         width = -1;\n   }\n\n   if (width != -1)\n      width = max (width, child->getMinWidth (NULL, forceValue));\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"=> %d\", width);\n   DBG_OBJ_LEAVE ();\n\n   return width;  \n}\n\nint OOFPosAbsLikeMgr::getAvailHeightOfChild (Widget *child, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0,\n                  \"OOFPositionedMgr/getAvailHeightOfChild\", \"%p, %s\",\n                  child, forceValue ? \"true\" : \"false\");\n\n   int height;\n\n   if (child->getStyle()->height == style::LENGTH_AUTO &&\n       child->getStyle()->minHeight == style::LENGTH_AUTO &&\n       child->getStyle()->maxHeight == style::LENGTH_AUTO) {\n      // TODO This should (perhaps?) only used when 'height' is undefined.\n      // TODO Is \"boxDiffHeight()\" correct here?\n      DBG_OBJ_MSG (\"resize.oofm\", 1, \"no specification\");\n      if (forceValue) {\n         int availHeight = container->getAvailHeight (true), top, bottom;\n\n         // Regard undefined values as 0:\n         if (!getPosTop (child, availHeight, &top)) top = 0;\n         if (!getPosBottom (child, availHeight, &bottom)) bottom = 0;\n\n         height =\n            max (availHeight - containerBoxDiffHeight () - top - bottom, 0);\n      } else\n         height = -1;\n   } else {\n      if (forceValue) {\n         int availHeight = container->getAvailHeight (true);\n         height = child->calcHeight (child->getStyle()->height, true,\n                                     availHeight - containerBoxDiffHeight (),\n                                     NULL, true);\n      } else\n         height = -1;\n   }\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"=> %d\", height);\n   DBG_OBJ_LEAVE ();\n\n   return height;  \n}\n\nbool OOFPosAbsLikeMgr::posXAbsolute (Child *child)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"posXAbsolute\", \"[%p]\", child->widget);\n   bool b =\n      (style::isAbsLength (child->widget->getStyle()->left) ||\n       style::isPerLength (child->widget->getStyle()->left)) &&\n      (style::isAbsLength (child->widget->getStyle()->right) ||\n       style::isPerLength (child->widget->getStyle()->right));\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr (b));\n   return b;\n}\n\nbool OOFPosAbsLikeMgr::posYAbsolute (Child *child)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"posYAbsolute\", \"[%p]\", child->widget);\n   bool b =\n      (style::isAbsLength (child->widget->getStyle()->top) ||\n       style::isPerLength (child->widget->getStyle()->top)) &&\n      (style::isAbsLength (child->widget->getStyle()->bottom) ||\n       style::isPerLength (child->widget->getStyle()->bottom));\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr (b));\n   return b;\n}\n\nvoid OOFPosAbsLikeMgr::calcPosAndSizeChildOfChild (Child *child, int refWidth,\n                                                   int refHeight, int *xPtr,\n                                                   int *yPtr, int *widthPtr,\n                                                   int *ascentPtr,\n                                                   int *descentPtr)\n{\n   // *xPtr and *yPtr refer to reference area; caller must adjust them.\n\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"calcPosAndSizeChildOfChild\",\n                  \"[%p], %d, %d, ...\", child->widget, refWidth, refHeight);\n\n   // TODO (i) Consider {min|max}-{width|heigt}. (ii) Height is always\n   // apportioned to descent (ascent is preserved), which makes sense\n   // when the children are textblocks. (iii) Consider minimal width\n   // (getMinWidth)?\n\n   Requisition childRequisition;\n   child->widget->sizeRequest (&childRequisition);\n\n   calcHPosAndSizeChildOfChild (child, refWidth, childRequisition.width,\n                                xPtr, widthPtr);\n   calcVPosAndSizeChildOfChild (child, refHeight, childRequisition.ascent,\n                                childRequisition.descent, yPtr, ascentPtr,\n                                descentPtr);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFPosAbsLikeMgr::calcHPosAndSizeChildOfChild (Child *child, int refWidth,\n                                                    int origChildWidth,\n                                                    int *xPtr, int *widthPtr)\n{\n   assert (refWidth != -1 || (xPtr == NULL && widthPtr == NULL));\n\n   int width;\n   bool widthDefined;\n   if (style::isAbsLength (child->widget->getStyle()->width)) {\n      DBG_OBJ_MSGF (\"resize.oofm\", 1, \"absolute width: %dpx\",\n                    style::absLengthVal (child->widget->getStyle()->width));\n      width = style::absLengthVal (child->widget->getStyle()->width)\n         + child->widget->boxDiffWidth ();\n      widthDefined = true;\n   } else if (style::isPerLength (child->widget->getStyle()->width)) {\n      DBG_OBJ_MSGF (\"resize.oofm\", 1, \"percentage width: %g%%\",\n                    100 * style::perLengthVal_useThisOnlyForDebugging\n                             (child->widget->getStyle()->width));\n      width = style::multiplyWithPerLength (refWidth,\n                                             child->widget->getStyle()->width)\n         + child->widget->boxDiffWidth ();\n      widthDefined = true;\n   } else {\n      DBG_OBJ_MSG (\"resize.oofm\", 1, \"width not specified\");\n      width = origChildWidth;\n      widthDefined = false;\n   }\n\n   int left, right;\n   bool leftDefined = getPosLeft (child->widget, refWidth, &left),\n      rightDefined = getPosRight (child->widget, refWidth, &right);\n   DBG_OBJ_MSGF (\"resize.oofm\", 1,\n                 \"=> left = %d, right = %d, width = %d (defined: %s)\",\n                 left, right, width, widthDefined ? \"true\" : \"false\");\n\n   if (xPtr) {\n      if (!leftDefined && !rightDefined)\n         *xPtr = generatorPosX (child) + child->x;\n      else {\n         if (!leftDefined && rightDefined)\n            *xPtr = refWidth - width - right - containerBoxRestWidth ();\n         else if (leftDefined && !rightDefined)\n            *xPtr = left + containerBoxOffsetX ();\n         else {\n            *xPtr = left;\n            if (!widthDefined) {\n               width = refWidth - (left + right + containerBoxDiffWidth ());\n               DBG_OBJ_MSGF (\"resize.oofm\", 0, \"=> width (corrected) = %d\",\n                             width);\n            }\n         }\n      }\n\n      DBG_OBJ_MSGF (\"resize.oofm\", 0, \"=> x = %d\", *xPtr);\n   }\n\n   if (widthPtr)\n      *widthPtr = width;\n}\n\nvoid OOFPosAbsLikeMgr::calcVPosAndSizeChildOfChild (Child *child, int refHeight,\n                                                    int origChildAscent,\n                                                    int origChildDescent,\n                                                    int *yPtr, int *ascentPtr,\n                                                    int *descentPtr)\n{\n   assert (refHeight != -1 ||\n           (yPtr == NULL && ascentPtr == NULL && descentPtr == NULL));\n\n   int ascent = origChildAscent, descent = origChildDescent;\n   bool heightDefined;\n\n   if (style::isAbsLength (child->widget->getStyle()->height)) {\n      DBG_OBJ_MSGF (\"resize.oofm\", 1, \"absolute height: %dpx\",\n                    style::absLengthVal (child->widget->getStyle()->height));\n      int height = style::absLengthVal (child->widget->getStyle()->height)\n         + child->widget->boxDiffHeight ();\n      splitHeightPreserveAscent (height, &ascent, &descent);\n      heightDefined = true;\n   } else if (style::isPerLength (child->widget->getStyle()->height)) {\n      DBG_OBJ_MSGF (\"resize.oofm\", 1, \"percentage height: %g%%\",\n                    100 * style::perLengthVal_useThisOnlyForDebugging\n                             (child->widget->getStyle()->height));\n      int height =\n         style::multiplyWithPerLength (refHeight,\n                                       child->widget->getStyle()->height)\n         + child->widget->boxDiffHeight ();\n      splitHeightPreserveAscent (height, &ascent, &descent);\n      heightDefined = true;\n   } else {\n      DBG_OBJ_MSG (\"resize.oofm\", 1, \"height not specified\");\n      heightDefined = false;\n   }\n\n   int top, bottom;\n   bool topDefined = getPosTop (child->widget, refHeight, &top),\n      bottomDefined = getPosBottom (child->widget, refHeight, &bottom);\n   DBG_OBJ_MSGF (\"resize.oofm\", 1,\n                 \"=> top = %d, bottom = %d, height = %d + %d (defined: %s)\",\n                 top, bottom, ascent, descent,\n                 heightDefined ? \"true\" : \"false\");\n\n   if (yPtr) {\n      if (!topDefined && !bottomDefined)\n         *yPtr = generatorPosY (child) + child->y;\n      else {\n         if (!topDefined && bottomDefined)\n            *yPtr = refHeight - (ascent + descent) - bottom\n               - containerBoxDiffHeight ();\n         else if (topDefined && !bottomDefined)\n            *yPtr = top + containerBoxOffsetY ();\n         else {\n            *yPtr = top;\n            if (!heightDefined) {\n               int height =\n                  refHeight - (top + bottom + containerBoxDiffHeight ());\n               splitHeightPreserveAscent (height, &ascent, &descent);\n               DBG_OBJ_MSGF (\"resize.oofm\", 0,\n                             \"=> ascent + descent (corrected) = %d + %d\",\n                             ascent, descent);\n            }\n         }\n      }\n\n      DBG_OBJ_MSGF (\"resize.oofm\", 0, \"=> y = %d\", *yPtr);\n   }\n\n   if (ascentPtr)\n      *ascentPtr = ascent;\n   if (descentPtr)\n      *descentPtr = descent;\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/oofposabslikemgr.hh",
    "content": "#ifndef __DW_OOFPOSABSLIKEMGR_HH__\n#define __DW_OOFPOSABSLIKEMGR_HH__\n\n#include \"oofpositionedmgr.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\nclass OOFPosAbsLikeMgr: public OOFPositionedMgr\n{\nprotected:\n   virtual int containerBoxOffsetX () = 0;\n   virtual int containerBoxOffsetY () = 0;\n   virtual int containerBoxRestWidth () = 0;\n   virtual int containerBoxRestHeight () = 0;\n\n   inline int containerBoxDiffWidth ()\n   { return containerBoxOffsetX () + containerBoxRestWidth (); }\n   inline int containerBoxDiffHeight ()\n   { return containerBoxOffsetY () + containerBoxRestHeight (); }\n\n   bool haveExtremesChanged ();\n\n   void sizeAllocateChildren ();\n\n   bool posXAbsolute (Child *child);\n   bool posYAbsolute (Child *child);\n\n   void calcPosAndSizeChildOfChild (Child *child, int refWidth, int refHeight,\n                                    int *xPtr, int *yPtr, int *widthPtr,\n                                    int *ascentPtr, int *descentPtr);\n   void calcHPosAndSizeChildOfChild (Child *child, int refWidth,\n                                     int origChildWidth, int *xPtr,\n                                     int *widthPtr);\n   void calcVPosAndSizeChildOfChild (Child *child, int refHeight,\n                                     int origChildAscent, int origChildDescent,\n                                     int *yPtr, int *ascentPtr,\n                                     int *descentPtr);\n\n\npublic:\n   OOFPosAbsLikeMgr (OOFAwareWidget *container);\n   ~OOFPosAbsLikeMgr ();\n\n   void calcWidgetRefSize (core::Widget *widget, core::Requisition *size);\n\n   void getSize (core::Requisition *containerReq, int *oofWidth,\n                 int *oofHeight);\n   void getExtremes (core::Extremes *containerExtr,\n                     int *oofMinWidth, int *oofMaxWidth);\n\n   int getAvailWidthOfChild (core::Widget *child, bool forceValue);\n   int getAvailHeightOfChild (core::Widget *child, bool forceValue);\n};\n\n} // namespace oof\n\n} // namespace dw\n\n#endif // __DW_OOFPOSABSLIKEMGR_HH__\n"
  },
  {
    "path": "dw/oofposabsmgr.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"oofposabsmgr.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\nOOFPosAbsMgr::OOFPosAbsMgr (OOFAwareWidget *container) :\n   OOFPosAbsLikeMgr (container)\n{\n   DBG_OBJ_CREATE (\"dw::oof::OOFPosAbsMgr\");\n}\n\nOOFPosAbsMgr::~OOFPosAbsMgr ()\n{\n   DBG_OBJ_DELETE ();\n}\n\n// Comment for all containerBox* implementations: for the toplevel\n// widget, assume margin = border = 0 (should perhaps set so when\n// widgets are constructed), so that the padding area is actually the\n// allocation.\n\nint OOFPosAbsMgr::containerBoxOffsetX ()\n{\n   return container->getParent () ?\n      container->boxOffsetX () - container->getStyle()->padding.left : 0;\n}\n\nint OOFPosAbsMgr::containerBoxOffsetY ()\n{\n   return container->getParent () ?\n      container->boxOffsetY () - container->getStyle()->padding.top : 0;\n}\n\nint OOFPosAbsMgr::containerBoxRestWidth ()\n{\n   return container->getParent () ?\n      container->boxRestWidth () - container->getStyle()->padding.right : 0;\n}\n\nint OOFPosAbsMgr::containerBoxRestHeight ()\n{\n   return container->getParent () ?\n      container->boxRestHeight () - container->getStyle()->padding.bottom : 0;\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/oofposabsmgr.hh",
    "content": "#ifndef __DW_OOFPOSABSMGR_HH__\n#define __DW_OOFPOSABSMGR_HH__\n\n#include \"oofposabslikemgr.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\nclass OOFPosAbsMgr: public OOFPosAbsLikeMgr\n{\nprotected:\n   int containerBoxOffsetX ();\n   int containerBoxOffsetY ();\n   int containerBoxRestWidth ();\n   int containerBoxRestHeight ();\n\npublic:\n   OOFPosAbsMgr (OOFAwareWidget *container);\n   ~OOFPosAbsMgr ();\n};\n\n} // namespace oof\n\n} // namespace dw\n\n#endif // __DW_OOFPOSABSMGR_HH__\n"
  },
  {
    "path": "dw/oofposfixedmgr.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"oofposfixedmgr.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\nOOFPosFixedMgr::OOFPosFixedMgr (OOFAwareWidget *container) :\n   OOFPosAbsLikeMgr (container)\n{\n   DBG_OBJ_CREATE (\"dw::oof::OOFPosFixedMgr\");\n}\n\nOOFPosFixedMgr::~OOFPosFixedMgr ()\n{\n   DBG_OBJ_DELETE ();\n}\n\nint OOFPosFixedMgr::containerBoxOffsetX ()\n{\n   return 0;\n}\n\nint OOFPosFixedMgr::containerBoxOffsetY ()\n{\n   return 0;\n}\n\nint OOFPosFixedMgr::containerBoxRestWidth ()\n{\n   return 0;\n}\n\nint OOFPosFixedMgr::containerBoxRestHeight ()\n{\n   return 0;\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/oofposfixedmgr.hh",
    "content": "#ifndef __DW_OOFPOSFIXEDMGR_HH__\n#define __DW_OOFPOSFIXEDMGR_HH__\n\n#include \"oofposabslikemgr.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\nclass OOFPosFixedMgr: public OOFPosAbsLikeMgr\n{\nprotected:\n   int containerBoxOffsetX ();\n   int containerBoxOffsetY ();\n   int containerBoxRestWidth ();\n   int containerBoxRestHeight ();\n\npublic:\n   OOFPosFixedMgr (OOFAwareWidget *container);\n   ~OOFPosFixedMgr ();\n};\n\n} // namespace oof\n\n} // namespace dw\n\n#endif // __DW_OOFPOSFIXEDMGR_HH__\n"
  },
  {
    "path": "dw/oofpositionedmgr.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2013-2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"oofpositionedmgr.hh\"\n#include \"../lout/debug.hh\"\n\nusing namespace lout::object;\nusing namespace lout::container::typed;\nusing namespace lout::misc;\nusing namespace dw::core;\nusing namespace dw::core::style;\n\nnamespace dw {\n\nnamespace oof {\n\nOOFPositionedMgr::Child::Child (core::Widget *widget, OOFAwareWidget *generator,\n                                int externalIndex)\n{\n   this->widget = widget;\n   this->generator = generator;\n   this->externalIndex = externalIndex;\n   \n   x = y = 0;\n\n   // Initially, this child does not actually have been considered,\n   // but since adding a new element will force a size/extremes\n   // calculation, this is equivalent.\n   consideredForSize = consideredForExtremes = true;\n}\n\nOOFPositionedMgr::OOFPositionedMgr (OOFAwareWidget *container)\n{\n   DBG_OBJ_CREATE (\"dw::oof::OOFPositionedMgr\");\n\n   this->container = (OOFAwareWidget*)container;\n   children = new Vector<Child> (1, false);\n   childrenByWidget = new HashTable<TypedPointer<Widget>, Child> (true, true);\n\n   if(container->wasAllocated()) {\n      containerAllocationState = WAS_ALLOCATED;\n      containerAllocation = *(container->getAllocation());\n   } else {\n      containerAllocationState = NOT_ALLOCATED;\n      containerAllocation.x = -1;\n      containerAllocation.y = -1;\n      containerAllocation.width = 1;\n      containerAllocation.ascent = 1;\n      containerAllocation.descent = 0;\n   }\n\n   DBG_OBJ_SET_NUM (\"children.size\", children->size());\n}\n\nOOFPositionedMgr::~OOFPositionedMgr ()\n{\n   delete children;\n   delete childrenByWidget;\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid OOFPositionedMgr::sizeAllocateStart (OOFAwareWidget *caller,\n                                          Allocation *allocation)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"sizeAllocateStart\",\n                  \"%p, (%d, %d, %d * (%d + %d))\",\n                  caller, allocation->x, allocation->y, allocation->width,\n                  allocation->ascent, allocation->descent);\n\n   if (caller == container) {\n      if (containerAllocationState == NOT_ALLOCATED)\n         containerAllocationState = IN_ALLOCATION;\n      containerAllocation = *allocation;\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n\nvoid OOFPositionedMgr::sizeAllocateEnd (OOFAwareWidget *caller)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"sizeAllocateEnd\", \"%p\", caller);\n\n   if (caller == container) {\n      sizeAllocateChildren ();\n\n      bool extremesChanged = !allChildrenConsideredForExtremes ();\n      if (extremesChanged || doChildrenExceedContainer () ||\n          !allChildrenConsideredForSize ())\n         container->oofSizeChanged (extremesChanged);\n      \n      containerAllocationState = WAS_ALLOCATED;\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nbool OOFPositionedMgr::doChildrenExceedContainer ()\n{\n   DBG_OBJ_ENTER0 (\"resize.oofm\", 0, \"doChildrenExceedContainer\");\n\n   // This method is called to determine whether the *requisition* of\n   // the container must be recalculated. So, we check the allocations\n   // of the children against the *requisition* of the container,\n   // which may (e. g. within tables) differ from the new allocation.\n   // (Generally, a widget may allocated at a different size.)\n\n   Requisition containerReq;\n   container->sizeRequest (&containerReq);\n   bool exceeds = false;\n\n   DBG_OBJ_MSG_START ();\n\n   for (int i = 0; i < children->size () && !exceeds; i++) {\n      Child *child = children->get (i);\n      Allocation *childAlloc = child->widget->getAllocation ();\n      DBG_OBJ_MSGF (\"resize.oofm\", 2,\n                    \"Does childAlloc = (%d, %d, %d * %d) exceed container \"\n                    \"alloc+req = (%d, %d, %d * %d)?\",\n                    childAlloc->x, childAlloc->y, childAlloc->width,\n                    childAlloc->ascent + childAlloc->descent,\n                    containerAllocation.x, containerAllocation.y,\n                    containerReq.width,\n                    containerReq.ascent + containerReq.descent);\n      if (childAlloc->x + childAlloc->width\n          > containerAllocation.x + containerReq.width ||\n          childAlloc->y + childAlloc->ascent + childAlloc->descent\n          > containerAllocation.y +\n            containerReq.ascent + containerReq.descent) {\n         exceeds = true;\n         DBG_OBJ_MSG (\"resize.oofm\", 2, \"Yes.\");\n      } else\n         DBG_OBJ_MSG (\"resize.oofm\", 2, \"No.\");\n   }\n\n   DBG_OBJ_MSG_END ();\n\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"=> %s\", exceeds ? \"true\" : \"false\");\n   DBG_OBJ_LEAVE ();\n\n   return exceeds;\n}\n\nvoid OOFPositionedMgr::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n\n   for (int i = 0; i < children->size(); i++)\n      children->get(i)->widget->containerSizeChanged ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFPositionedMgr::draw (View *view, Rectangle *area,\n                             DrawingContext *context)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"draw\", \"%d, %d, %d * %d\",\n                  area->x, area->y, area->width, area->height);\n\n   for (int i = 0; i < children->size(); i++) {\n      Child *child = children->get(i);\n\n      Rectangle childArea;\n      if (!context->hasWidgetBeenProcessedAsInterruption (child->widget) &&\n          !StackingContextMgr::handledByStackingContextMgr (child->widget) &&\n          child->widget->intersects (container, area, &childArea))\n         child->widget->draw (view, &childArea, context);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFPositionedMgr::addWidgetInFlow (OOFAwareWidget *widget,\n                                        OOFAwareWidget *parent,\n                                        int externalIndex)\n{\n}\n\nint OOFPositionedMgr::addWidgetOOF (Widget *widget, OOFAwareWidget *generator,\n                                    int externalIndex)\n{\n   DBG_OBJ_ENTER (\"construct.oofm\", 0, \"addWidgetOOF\", \"%p, %p, %d\",\n                  widget, generator, externalIndex);\n\n   Child *child = new Child (widget, generator, externalIndex);\n   children->put (child);\n   childrenByWidget->put (new TypedPointer<Widget> (widget), child);\n\n   int subRef = children->size() - 1;\n   DBG_OBJ_SET_NUM (\"children.size\", children->size());\n   DBG_OBJ_ARRSET_PTR (\"children\", children->size() - 1, widget);\n\n   DBG_OBJ_SET_PTR_O (widget, \"<Positioned>.generator\", generator);\n   DBG_OBJ_SET_NUM_O (widget, \"<Positioned>.externalIndex\", externalIndex);\n\n   DBG_OBJ_MSGF (\"construct.oofm\", 1, \"=> %d\", subRef);\n   DBG_OBJ_LEAVE ();\n   return subRef;\n}\n\nvoid OOFPositionedMgr::moveExternalIndices (OOFAwareWidget *generator,\n                                            int oldStartIndex, int diff)\n{\n   for (int i = 0; i < children->size (); i++) {\n      Child *child = children->get (i);\n      if (child->externalIndex >= oldStartIndex) {\n         child->externalIndex += diff;\n         DBG_OBJ_SET_NUM_O (child->widget, \"<Positioned>.externalIndex\",\n                            child->externalIndex);\n      }\n   }\n}\n\nvoid OOFPositionedMgr::markSizeChange (int ref)\n{\n}\n\n\nvoid OOFPositionedMgr::markExtremesChange (int ref)\n{\n}\n\nWidget *OOFPositionedMgr::getWidgetAtPoint (int x, int y,\n                                            GettingWidgetAtPointContext\n                                            *context)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"getWidgetAtPoint\", \"%d, %d\", x, y);\n\n   Widget *widgetAtPoint = NULL;\n   \n   for (int i = children->size() - 1; widgetAtPoint == NULL && i >= 0; i--) {\n      Widget *childWidget = children->get(i)->widget;\n      if (!context->hasWidgetBeenProcessedAsInterruption (childWidget) &&\n          !StackingContextMgr::handledByStackingContextMgr (childWidget))\n         widgetAtPoint = childWidget->getWidgetAtPoint (x, y, context);\n   }\n\n   DBG_OBJ_MSGF (\"events\", 0, \"=> %p\", widgetAtPoint);\n   DBG_OBJ_LEAVE ();\n\n   return widgetAtPoint;\n}\n\nvoid OOFPositionedMgr::tellPosition1 (Widget *widget, int x, int y)\n{\n}\n\nvoid OOFPositionedMgr::tellPosition2 (Widget *widget, int x, int y)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"tellPosition2\", \"%p, %d, %d\",\n                  widget, x, y);\n\n   TypedPointer<Widget> key (widget);\n   Child *child = childrenByWidget->get (&key);\n   assert (child);\n\n   child->x = x;\n   child->y = y;\n\n   DBG_OBJ_SET_NUM_O (child->widget, \"<Positioned>.x\", x);\n   DBG_OBJ_SET_NUM_O (child->widget, \"<Positioned>.y\", y);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFPositionedMgr::tellIncompletePosition1 (Widget *generator,\n                                                Widget *widget, int x, int y)\n{\n   // Nothing to do.\n}\n\nvoid OOFPositionedMgr::tellIncompletePosition2 (Widget *generator,\n                                                Widget *widget, int x, int y)\n{\n   // TODO\n}\n\nbool OOFPositionedMgr::containerMustAdjustExtraSpace ()\n{\n   return true;\n}\n\nint OOFPositionedMgr::getLeftBorder (int y, int h, OOFAwareWidget *lastGen,\n                                     int lastExtIndex)\n{\n   return 0;\n}\n\nint OOFPositionedMgr::getRightBorder (int y, int h, OOFAwareWidget *lastGen,\n                                      int lastExtIndex)\n{\n   return 0;\n}\n\nbool OOFPositionedMgr::hasFloatLeft (int y, int h, OOFAwareWidget *lastGen,\n                                     int lastExtIndex)\n{\n   return false;\n}\n\nbool OOFPositionedMgr::hasFloatRight (int y, int h, OOFAwareWidget *lastGen,\n                                      int lastExtIndex)\n{\n   return false;\n}\n\n\nint OOFPositionedMgr::getLeftFloatHeight (int y, int h, OOFAwareWidget *lastGen,\n                                          int lastExtIndex)\n{\n   return 0;\n}\n\nint OOFPositionedMgr::getRightFloatHeight (int y, int h,\n                                           OOFAwareWidget *lastGen,\n                                           int lastExtIndex)\n{\n   return 0;\n}\n\nint OOFPositionedMgr::getClearPosition (OOFAwareWidget *widget)\n{\n   return 0;\n}\n\nbool OOFPositionedMgr::affectsLeftBorder (Widget *widget)\n{\n   return false;\n}\n\nbool OOFPositionedMgr::affectsRightBorder (Widget *widget)\n{\n   return false;\n}\n\nbool OOFPositionedMgr::mayAffectBordersAtAll ()\n{\n   return false;\n}\n\nbool OOFPositionedMgr::dealingWithSizeOfChild (Widget *child)\n{\n   return true;\n}\n\nint OOFPositionedMgr::getNumWidgets ()\n{\n   return children->size();\n}\n\nWidget *OOFPositionedMgr::getWidget (int i)\n{\n   return children->get(i)->widget;\n}\n\nbool OOFPositionedMgr::getPosBorder (style::Length cssValue, int refLength,\n                                     int *result)\n{\n   if (style::isAbsLength (cssValue)) {\n      *result = style::absLengthVal (cssValue);\n      return true;\n   }  else if (style::isPerLength (cssValue)) {\n      *result = style::multiplyWithPerLength (refLength, cssValue);\n      return true;\n   } else\n      // \"false\" means \"undefined\":\n      return false;\n}\n\nbool OOFPositionedMgr::allChildrenConsideredForSize ()\n{   \n   for (int i = 0; i < children->size(); i++)\n      if (!children->get(i)->consideredForSize)\n         return false;\n   return true;\n}\n\nbool OOFPositionedMgr::allChildrenConsideredForExtremes ()\n{   \n   for (int i = 0; i < children->size(); i++)\n      if (!children->get(i)->consideredForExtremes)\n         return false;\n   return true;\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/oofpositionedmgr.hh",
    "content": " #ifndef __DW_OOFPOSITIONEDMGR_HH__\n#define __DW_OOFPOSITIONEDMGR_HH__\n\n#include \"outofflowmgr.hh\"\n#include \"oofawarewidget.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\nclass OOFPositionedMgr: public OutOfFlowMgr\n{\nprotected:\n   class Child: public lout::object::Object\n   {\n   public:\n      core::Widget *widget;\n      OOFAwareWidget *generator;\n      int externalIndex, x, y;\n      bool consideredForSize, consideredForExtremes;\n\n      Child (core::Widget *widget, OOFAwareWidget *generator,\n             int externalIndex);\n   };\n\n   OOFAwareWidget *container;\n   core::Allocation containerAllocation;\n   enum { NOT_ALLOCATED, IN_ALLOCATION, WAS_ALLOCATED }\n      containerAllocationState;\n\n   lout::container::typed::Vector<Child> *children;\n   lout::container::typed::HashTable<lout::object::TypedPointer\n                                        <dw::core::Widget>,\n                                     Child> *childrenByWidget;\n\n   inline bool getPosLeft (core::Widget *child, int availWidth, int *result)\n   { return getPosBorder (child->getStyle()->left, availWidth, result); }\n   inline bool getPosRight (core::Widget *child, int availWidth, int *result)\n   { return getPosBorder (child->getStyle()->right, availWidth, result); }\n   inline bool getPosTop (core::Widget *child, int availHeight, int *result)\n   { return getPosBorder (child->getStyle()->top, availHeight, result); }\n   inline bool getPosBottom (core::Widget *child, int availHeight, int *result)\n   { return getPosBorder (child->getStyle()->bottom, availHeight, result); }\n\n   bool getPosBorder (core::style::Length cssValue, int refLength, int *result);\n\n   bool allChildrenConsideredForSize ();\n   bool allChildrenConsideredForExtremes ();\n\n   bool doChildrenExceedContainer ();\n\n   virtual void sizeAllocateChildren () = 0;\n   virtual bool posXAbsolute (Child *child) = 0;\n   virtual bool posYAbsolute (Child *child) = 0;\n\n   inline bool generatorPosDefined (Child *child) {\n      return child->generator == container ||\n         (containerAllocationState != NOT_ALLOCATED\n          && child->generator->wasAllocated ());\n   }\n   inline int generatorPosX (Child *child) {\n      assert (generatorPosDefined (child));\n      return child->generator == container ? 0 :\n         child->generator->getAllocation()->x - containerAllocation.x;\n   }\n   inline int generatorPosY (Child *child) {\n      assert (generatorPosDefined (child));\n      return child->generator == container ? 0 :\n         child->generator->getAllocation()->y - containerAllocation.y;\n   }\n   \n   inline bool posXDefined (Child *child)\n   { return posXAbsolute (child) || generatorPosDefined (child); }\n\n   inline bool posYDefined (Child *child)\n   { return posYAbsolute (child) || generatorPosDefined (child); }\n\npublic:\n   OOFPositionedMgr (OOFAwareWidget *container);\n   ~OOFPositionedMgr ();\n\n   void sizeAllocateStart (OOFAwareWidget *caller,\n                           core::Allocation *allocation);\n   void sizeAllocateEnd (OOFAwareWidget *caller);\n\n   void containerSizeChangedForChildren ();\n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n\n   void markSizeChange (int ref);\n   void markExtremesChange (int ref);\n   core::Widget *getWidgetAtPoint (int x, int y,\n                                   core::GettingWidgetAtPointContext *context);\n\n   void addWidgetInFlow (OOFAwareWidget *widget, OOFAwareWidget *parent,\n                         int externalIndex);\n   int addWidgetOOF (core::Widget *widget, OOFAwareWidget *generator,\n                     int externalIndex);\n   void moveExternalIndices (OOFAwareWidget *generator, int oldStartIndex,\n                             int diff);\n\n   void tellPosition1 (core::Widget *widget, int x, int y);\n   void tellPosition2 (core::Widget *widget, int x, int y);\n   void tellIncompletePosition1 (core::Widget *generator, core::Widget *widget,\n                                 int x, int y);\n   void tellIncompletePosition2 (core::Widget *generator, core::Widget *widget,\n                                 int x, int y);\n   \n\n   bool containerMustAdjustExtraSpace ();\n\n   int getLeftBorder (int y, int h, OOFAwareWidget *lastGen, int lastExtIndex);\n   int getRightBorder (int y, int h, OOFAwareWidget *lastGen, int lastExtIndex);\n\n   bool hasFloatLeft (int y, int h, OOFAwareWidget *lastGen, int lastExtIndex);\n   bool hasFloatRight (int y, int h, OOFAwareWidget *lastGen, int lastExtIndex);\n\n   int getLeftFloatHeight (int y, int h, OOFAwareWidget *lastGen,\n                           int lastExtIndex);\n   int getRightFloatHeight (int y, int h, OOFAwareWidget *lastGen,\n                            int lastExtIndex);\n\n   int getClearPosition (OOFAwareWidget *widget);\n\n   bool affectsLeftBorder (core::Widget *widget);\n   bool affectsRightBorder (core::Widget *widget);\n   bool mayAffectBordersAtAll ();\n\n   bool dealingWithSizeOfChild (core::Widget *child);\n\n   int getNumWidgets ();\n   core::Widget *getWidget (int i);\n};\n\n} // namespace oof\n\n} // namespace dw\n\n#endif // __DW_OOFPOSITIONEDMGR_HH__\n"
  },
  {
    "path": "dw/oofposrelmgr.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2015 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"oofposrelmgr.hh\"\n\nusing namespace dw::core;\nusing namespace lout::object;\nusing namespace lout::misc;\n\nnamespace dw {\n\nnamespace oof {\n\nOOFPosRelMgr::OOFPosRelMgr (OOFAwareWidget *container) :\n   OOFPositionedMgr (container)\n{\n   DBG_OBJ_CREATE (\"dw::oof::OOFPosRelMgr\");\n}\n\nOOFPosRelMgr::~OOFPosRelMgr ()\n{\n   DBG_OBJ_DELETE ();\n}\n\n\nvoid OOFPosRelMgr::markSizeChange (int ref)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"markSizeChange\", \"%d\", ref);\n   Child *child = children->get(ref);\n   DBG_OBJ_MSGF (\"resize.oofm\", 1, \"generator = %p, externalIndex = %d\",\n                 child->generator, child->externalIndex);\n   child->generator->widgetRefSizeChanged (child->externalIndex);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFPosRelMgr::markExtremesChange (int ref)\n{\n}\n\nvoid OOFPosRelMgr::calcWidgetRefSize (Widget *widget, Requisition *size)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"calcWidgetRefSize\", \"%p\", widget);\n\n   widget->sizeRequest (size);\n\n   // In some cases, the widget has been enlarged for widgets out of\n   // flow. Partly, this is done by adding \"extra space\"; however, at\n   // this point, the extra space is not relevant here. See\n   // \"oofawarewidget.cc\" for a calculation of RequisitionWithoutOOF.\n   // (Notice also that Widget::sizeRequest has to be called in all\n   // cases.)\n\n   if (widget->instanceOf (OOFAwareWidget::CLASS_ID))\n      *size = *((OOFAwareWidget*)widget)->getRequisitionWithoutOOF ();\n\n   \n   DBG_OBJ_LEAVE_VAL (\"%d * (%d + %d)\",\n                      size->width, size->ascent, size->descent);\n}\n\n\nvoid OOFPosRelMgr::sizeAllocateChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize.oofm\", 0, \"sizeAllocateChildren\");\n\n   for (int i = 0; i < children->size (); i++) {\n      Child *child = children->get(i);\n         \n      Requisition childReq;      \n      child->widget->sizeRequest (&childReq);\n         \n      Allocation childAlloc;\n      childAlloc.x = containerAllocation.x + getChildPosX (child);\n      childAlloc.y = containerAllocation.y + getChildPosY (child);\n      childAlloc.width = childReq.width;\n      childAlloc.ascent = childReq.ascent;\n      childAlloc.descent = childReq.descent;\n      child->widget->sizeAllocate (&childAlloc);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid OOFPosRelMgr::getSize (Requisition *containerReq, int *oofWidth,\n                            int *oofHeight)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getSize\", \"%d * (%d + %d)\",\n                  containerReq->width, containerReq->ascent,\n                  containerReq->descent);\n\n   *oofWidth = *oofHeight = 0;\n\n   for (int i = 0; i < children->size (); i++) {\n      Child *child = children->get(i);\n\n      // Children whose position cannot be determined will be\n      // considered later in sizeAllocateEnd.\n      if (posXDefined (child) && posYDefined (child)) {\n         Requisition childReq;\n         child->widget->sizeRequest (&childReq);\n         *oofWidth = max (*oofWidth, getChildPosX (child) + childReq.width);\n         *oofHeight = max (*oofHeight,\n                           getChildPosY (child) + childReq.ascent\n                           + childReq.descent);\n         \n         child->consideredForSize = true;\n      } else\n         child->consideredForSize = false;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d * %d\", *oofWidth, *oofHeight);\n}\n\nvoid OOFPosRelMgr::getExtremes (Extremes *containerExtr, int *oofMinWidth,\n                                int *oofMaxWidth)\n{\n   *oofMinWidth = *oofMaxWidth = 0;\n\n   for (int i = 0; i < children->size (); i++) {\n      Child *child = children->get(i);\n\n      // Children whose position cannot be determined will be\n      // considered later in sizeAllocateEnd.\n      if (posXDefined (child)) {\n         Extremes childExtr;      \n         child->widget->getExtremes (&childExtr);\n         \n         // Put the extremes of the container in relation to the extremes\n         // of the child, as in OOFPosAbsLikeMgr::getExtremes (see\n         // comment there).\n         *oofMinWidth = max (*oofMinWidth,\n                             getChildPosX (child, containerExtr->minWidth)\n                             + childExtr.minWidth);\n         *oofMaxWidth = max (*oofMaxWidth,\n                             getChildPosX (child, containerExtr->maxWidth)\n                             + childExtr.maxWidth);\n\n         child->consideredForExtremes = true;\n      } else\n         child->consideredForExtremes = false;\n   }\n}\n\nbool OOFPosRelMgr::posXAbsolute (Child *child)\n{\n   return false;\n}\n\nbool OOFPosRelMgr::posYAbsolute (Child *child)\n{\n   return false;\n}\n\nint OOFPosRelMgr::getChildPosX (Child *child, int refWidth)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getChildPosX\", \"[%p], %d\",\n                  child->widget, refWidth);\n\n   int gx = generatorPosX (child);\n   int dim = getChildPosDim (child->widget->getStyle()->left,\n                             child->widget->getStyle()->right,\n                             child->x,\n                             refWidth\n                             - child->widget->getStyle()->boxDiffWidth ());\n\n   DBG_OBJ_LEAVE_VAL (\"%d + %d = %d\", gx, dim, gx + dim);\n   return gx + dim;\n}\n\n\nint OOFPosRelMgr::getChildPosY (Child *child, int refHeight)\n{\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getChildPosY\", \"[%p], %d\",\n                  child->widget, refHeight);\n\n   int gy = generatorPosY (child);\n   int dim = getChildPosDim (child->widget->getStyle()->top,\n                             child->widget->getStyle()->bottom,\n                             child->y,\n                             refHeight\n                             - child->widget->getStyle()->boxDiffHeight ());\n\n   DBG_OBJ_LEAVE_VAL (\"%d + %d = %d\", gy, dim, gy + dim);\n   return gy + dim;\n}\n\nint OOFPosRelMgr::getChildPosDim (style::Length posCssValue,\n                                  style::Length negCssValue, int refPos,\n                                  int refLength)\n{\n   // posCssValue refers to \"left\" or \"top\", negCssValue refers to \"right\" or\n   // \"bottom\". The former values are preferred (\"left\" over \"right\" etc.),\n   // which should later depend on the CSS value \"direction\".\n\n   DBG_OBJ_ENTER (\"resize.oofm\", 0, \"getChildPosDim\",\n                  \"<i>%d</i>, <i>%d</i>, %d, %d\",\n                  posCssValue, negCssValue, refPos, refLength);\n   \n   int diff;\n   if (getPosBorder (posCssValue, refLength, &diff))\n      DBG_OBJ_MSGF (\"resize.oofm\", 1, \"posCssValue: diff = %d\", diff);\n   else {\n      if (getPosBorder (negCssValue, refLength, &diff)) {\n         DBG_OBJ_MSGF (\"resize.oofm\", 1, \"negCssValue: diff = %d\", diff);\n         diff *= -1;\n      } else\n         diff = 0;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d + %d = %d\", refPos, diff, refPos + diff);\n   return refPos + diff;\n}\n\nbool OOFPosRelMgr::dealingWithSizeOfChild (Widget *child)\n{\n   return false;\n}\n\nint OOFPosRelMgr::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   notImplemented(\"OOFPosRelMgr::getAvailWidthOfChild\");\n   return 0;\n}\n\nint OOFPosRelMgr::getAvailHeightOfChild (Widget *child, bool forceValue)\n{\n   notImplemented (\"OOFPosRelMgr::getAvailHeightOfChild\");\n   return 0;\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/oofposrelmgr.hh",
    "content": "#ifndef __DW_OOFPOSRELMGR_HH__\n#define __DW_OOFPOSRELMGR_HH__\n\n#include \"oofpositionedmgr.hh\"\n\nnamespace dw {\n\nnamespace oof {\n\nclass OOFPosRelMgr: public OOFPositionedMgr\n{\nprotected:\n   void sizeAllocateChildren ();\n   bool posXAbsolute (Child *child);\n   bool posYAbsolute (Child *child);\n\n   int getChildPosX (Child *child, int refWidth);\n   int getChildPosY (Child *child, int refHeight);\n   int getChildPosDim (core::style::Length posCssValue,\n                       core::style::Length negCssValue, int refPos,\n                       int refLength);\n\n   inline int getChildPosX (Child *child) \n   { return getChildPosX (child, container->getAvailWidth (true)); }\n   inline int getChildPosY (Child *child) \n   { return getChildPosY (child, container->getAvailHeight (true)); }\n\npublic:\n   OOFPosRelMgr (OOFAwareWidget *container);\n   ~OOFPosRelMgr ();\n\n   void markSizeChange (int ref);\n   void markExtremesChange (int ref);\n   void calcWidgetRefSize (core::Widget *widget, core::Requisition *size);\n\n   void getSize (core::Requisition *containerReq, int *oofWidth,\n                 int *oofHeight);\n   void getExtremes (core::Extremes *containerExtr, int *oofMinWidth,\n                     int *oofMaxWidth);\n\n   bool dealingWithSizeOfChild (core::Widget *child);\n   int getAvailWidthOfChild (core::Widget *child, bool forceValue);\n   int getAvailHeightOfChild (core::Widget *child, bool forceValue);\n};\n\n} // namespace oof\n\n} // namespace dw\n\n#endif // __DW_OOFPOSRELMGR_HH__\n"
  },
  {
    "path": "dw/outofflowmgr.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2013-2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"outofflowmgr.hh\"\n#include \"oofawarewidget.hh\"\n#include \"../lout/debug.hh\"\n\n\nnamespace dw {\n\nnamespace oof {\n\nOutOfFlowMgr::OutOfFlowMgr ()\n{\n}\n\nOutOfFlowMgr::~OutOfFlowMgr ()\n{\n}\n\n} // namespace oof\n\n} // namespace dw\n"
  },
  {
    "path": "dw/outofflowmgr.hh",
    "content": "#ifndef __DW_OUTOFFLOWMGR_HH__\n#define __DW_OUTOFFLOWMGR_HH__\n\n#include \"core.hh\"\n\nnamespace dw {\n\n/**\n *  \\brief Out Of Flow. See \\ref dw-out-of-flow.\n */\nnamespace oof {\n\nclass OOFAwareWidget;\n\n/**\n * \\brief Represents additional data for OOF containers.\n */\nclass OutOfFlowMgr\n{\npublic:\n   OutOfFlowMgr ();\n   virtual ~OutOfFlowMgr ();\n\n   virtual void sizeAllocateStart (OOFAwareWidget *caller,\n                                   core::Allocation *allocation) = 0;\n   virtual void sizeAllocateEnd (OOFAwareWidget *caller) = 0;\n   virtual void containerSizeChangedForChildren () = 0;\n   virtual void draw (core::View *view, core::Rectangle *area,\n                      core::DrawingContext *context) = 0;\n\n   virtual void markSizeChange (int ref) = 0;\n   virtual void markExtremesChange (int ref) = 0;\n   virtual core::Widget *getWidgetAtPoint (int x, int y,\n                                           core::GettingWidgetAtPointContext\n                                           *context) = 0;\n\n   virtual void addWidgetInFlow (OOFAwareWidget *widget,\n                                 OOFAwareWidget *parent, int externalIndex) = 0;\n   virtual int addWidgetOOF (core::Widget *widget, OOFAwareWidget *generator,\n                             int externalIndex) = 0;\n   virtual void calcWidgetRefSize (core::Widget *widget,\n                                   core::Requisition *size) = 0;\n   virtual void moveExternalIndices (OOFAwareWidget *generator,\n                                     int oldStartIndex, int diff) = 0;\n   \n   /**\n    * \\brief Called before tellPosition2, see there for more.\n    */\n   virtual void tellPosition1 (core::Widget *widget, int x, int y) = 0;\n\n   /**\n    * \\brief Called after tellPosition1.\n    *\n    * An implementation should only implement either tellPosition1 or\n    * tellPosition2. Coordinates are relative to the *container*.\n    */\n   virtual void tellPosition2 (core::Widget *widget, int x, int y) = 0;\n\n   virtual void tellIncompletePosition1 (core::Widget *generator,\n                                         core::Widget *widget, int x, int y)\n      = 0;\n   virtual void tellIncompletePosition2 (core::Widget *generator,\n                                         core::Widget *widget, int x, int y)\n      = 0;\n\n   virtual void getSize (core::Requisition *containerReq, int *oofWidth,\n                         int *oofHeight) = 0;\n   virtual bool containerMustAdjustExtraSpace ()= 0;\n   virtual void getExtremes (core::Extremes *containerExtr, int *oofMinWidth,\n                             int *oofMaxWidth) = 0;\n\n   /**\n    * Get the left border for the vertical position of *y*, for a height\n    * of *h\", based on floats; relative to the *container*.\n    *\n    * The border includes marging/border/padding of the calling textblock\n    * but is 0 if there is no float, so a caller should also consider\n    * other borders.\n    */\n   virtual int getLeftBorder (int y, int h, OOFAwareWidget *lastGen,\n                              int lastExtIndex) = 0;\n\n   /**\n    * Get the right border for the vertical position of *y*, for a height\n    * of *h*, based on floats; relative to the *container*.\n    *\n    * See also getLeftBorder().\n    */\n   virtual int getRightBorder (int y, int h, OOFAwareWidget *lastGen,\n                               int lastExtIndex) = 0;\n\n   /**\n    * Return whether there is a float on the left side. *y* is\n    * relative to the *container*.\n    *\n    * See also getLeftBorder().\n    */\n   virtual bool hasFloatLeft (int y, int h, OOFAwareWidget *lastGen,\n                              int lastExtIndex) = 0;\n\n   /**\n    * Return whether there is a float on the right side. *y* is\n    * relative to the *container*.\n    *\n    * See also hasFloatLeft(), getLeftBorder();\n    */\n   virtual bool hasFloatRight (int y, int h, OOFAwareWidget *lastGen,\n                               int lastExtIndex) = 0;\n\n   /**\n    * Assuming there is a float on the left side, return the rest\n    * height of it. *y* is relative to the *container*.\n    *\n    * See also getLeftBorder().\n    */\n   virtual int getLeftFloatHeight (int y, int h, OOFAwareWidget *lastGen,\n                                   int lastExtIndex) = 0;\n\n   /**\n    * Assuming there is a float on the right side, return the rest\n    * height of it. *y* is relative to the *container*.\n    *\n    * See also getLeftFloatHeight(), getLeftBorder().\n    */\n   virtual int getRightFloatHeight (int y, int h, OOFAwareWidget *lastGen,\n                                    int lastExtIndex) = 0;\n   \n   virtual bool affectsLeftBorder (core::Widget *widget) = 0;\n   virtual bool affectsRightBorder (core::Widget *widget) = 0;\n   virtual bool mayAffectBordersAtAll () = 0;\n\n   /**\n    * Return value is relative to the *calling generator* (not container).\n    */\n   virtual int getClearPosition (OOFAwareWidget *widget) = 0;\n\n   virtual bool dealingWithSizeOfChild (core::Widget *child) = 0;\n   virtual int getAvailWidthOfChild (core::Widget *child, bool forceValue) = 0;\n   virtual int getAvailHeightOfChild (core::Widget *child, bool forceValue) = 0;\n   \n   // for iterators\n   virtual int getNumWidgets () = 0;\n   virtual core::Widget *getWidget (int i) = 0;\n};\n\n} // namespace oof\n\n} // namespace dw\n\n#endif // __DW_OUTOFFLOWMGR_HH__\n"
  },
  {
    "path": "dw/platform.hh",
    "content": "#ifndef __DW_PLATFORM_HH__\n#define __DW_PLATFORM_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief An interface to encapsulate some platform dependencies.\n *\n * \\sa\\ref dw-overview\n */\nclass Platform: public lout::object::Object\n{\npublic:\n   /*\n    * -----------------------------------\n    *    General\n    * -----------------------------------\n    */\n\n   /**\n    * \\brief This methods notifies the platform, that it has been attached to\n    *    a layout.\n    */\n   virtual void setLayout (Layout *layout) = 0;\n\n   /*\n    * -------------------------\n    *    Operations on views\n    * -------------------------\n    */\n\n   /**\n    * \\brief This methods notifies the platform, that a view has been attached\n    *    to the related layout.\n    */\n   virtual void attachView (View *view) = 0;\n\n   /**\n    * \\brief This methods notifies the platform, that a view has been detached\n    *    from the related layout.\n    */\n   virtual void detachView (View *view) = 0;\n\n   /*\n    * -----------------------------------\n    *    Platform dependent properties\n    * -----------------------------------\n    */\n\n   /**\n    * \\brief Return the width of a text, with a given length and font.\n    */\n   virtual int textWidth (style::Font *font, const char *text, int len) = 0;\n\n   /**\n    * \\brief Return the string resulting from transforming text to uppercase.\n    */\n   virtual char *textToUpper (const char *text, int len) = 0;\n\n   /**\n    * \\brief Return the string resulting from transforming text to lowercase.\n    */\n   virtual char *textToLower (const char *text, int len) = 0;\n\n   /**\n    * \\brief Return the index of the next glyph in string text.\n    */\n   virtual int nextGlyph (const char *text, int idx) = 0;\n\n   /**\n    * \\brief Return the index of the previous glyph in string text.\n    */\n   virtual int prevGlyph (const char *text, int idx) = 0;\n\n   /**\n    * \\brief Return screen resolution in x-direction.\n    */\n   virtual float dpiX () = 0;\n\n   /**\n    * \\brief Return screen resolution in y-direction.\n    */\n   virtual float dpiY () = 0;\n\n   /*\n    * ---------------------------------------------------------\n    *    These are to encapsulate some platform dependencies\n    * ---------------------------------------------------------\n    */\n\n   /**\n    * \\brief Add an idle function.\n    *\n    * An idle function is called once, when no other\n    * tasks are to be done (e.g. there are no events to process), and then\n    * removed from the queue. The return value is a number, which can be\n    * used in removeIdle below.\n    */\n   virtual int addIdle (void (Layout::*func) ()) = 0;\n\n   /**\n    * \\brief Remove an idle function, which has not been processed yet.\n    */\n   virtual void removeIdle (int idleId) = 0;\n\n   /*\n    * ---------------------\n    *    Style Resources\n    * ---------------------\n    */\n\n   /**\n    * \\brief Create a (platform dependent) font.\n    *\n    * Typically, within a platform, a sub class of dw::core::style::Font\n    * is defined, which holds more platform dependent data.\n    *\n    * Also, this method must fill the attributes \"font\" (when needed),\n    * \"ascent\", \"descent\", \"spaceSidth\" and \"xHeight\". If \"tryEverything\"\n    * is true, several methods should be used to use another font, when\n    * the requested font is not available. Passing false is typically done,\n    * if the caller wants to test different variations.\n    */\n   virtual style::Font *createFont (style::FontAttrs *attrs,\n                                    bool tryEverything) = 0;\n\n   virtual bool fontExists (const char *name) = 0;\n\n   /**\n    * \\brief Create a color resource for a given 0xrrggbb value.\n    */\n   virtual style::Color *createColor (int color) = 0;\n\n   /**\n    * \\brief Create a tooltip\n    */\n   virtual style::Tooltip *createTooltip (const char *text) = 0;\n\n   /**\n    * \\brief Cancel a tooltip (either shown or requested)\n    */\n   virtual void cancelTooltip () = 0;\n\n   /**\n    * \\brief Create a (platform speficic) image buffer.\n    *\n    * \"gamma\" is the value by which the image data is gamma-encoded.\n    */\n   virtual Imgbuf *createImgbuf (Imgbuf::Type type, int width, int height,\n                                 double gamma) = 0;\n\n   /**\n    * \\brief Copy selected text (0-terminated).\n    */\n   virtual void copySelection(const char *text) = 0;\n\n   /**\n    * ...\n    */\n   virtual ui::ResourceFactory *getResourceFactory () = 0;\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_PLATFORM_HH__\n"
  },
  {
    "path": "dw/preview.xbm",
    "content": "#define preview_width 11\n#define preview_height 11\nstatic unsigned char preview_bits[] = {\n   0x20, 0x00, 0x70, 0x00, 0x20, 0x00, 0x20, 0x00, 0x22, 0x02, 0xff, 0x07,\n   0x22, 0x02, 0x20, 0x00, 0x20, 0x00, 0x70, 0x00, 0x20, 0x00};\n"
  },
  {
    "path": "dw/regardingborder.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2015 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"regardingborder.hh\"\n\n#include <stdio.h>\n\nnamespace dw {\n\nint RegardingBorder::CLASS_ID = -1;\n\nRegardingBorder::RegardingBorder ()\n{\n   DBG_OBJ_CREATE (\"dw::RegardingBorder\");\n   registerName (\"dw::RegardingBorder\", &CLASS_ID);\n}\n\nRegardingBorder::~RegardingBorder ()\n{\n   DBG_OBJ_DELETE ();\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/regardingborder.hh",
    "content": "#ifndef __DW_REGARDINGBORDER_HH__\n#define __DW_REGARDINGBORDER_HH__\n\n#include \"oofawarewidget.hh\"\n\nnamespace dw {\n\n/**\n * \\brief Base class (rather a tag interface) for those widgets\n *    regarding borders defined by floats, and so allocated on the\n *    full width.\n */\nclass RegardingBorder: public oof::OOFAwareWidget\n{\npublic:\n   static int CLASS_ID;\n\n   RegardingBorder ();\n   ~RegardingBorder ();\n};\n\n} // namespace dw\n\n#endif // __DW_REGARDINGBORDER_HH__\n"
  },
  {
    "path": "dw/ruler.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"ruler.hh\"\n#include \"../lout/misc.hh\"\n\n#include <stdio.h>\n\nnamespace dw {\n\nint Ruler::CLASS_ID = -1;\n\nRuler::Ruler ()\n{\n   DBG_OBJ_CREATE (\"dw::Ruler\");\n   registerName (\"dw::Ruler\", &CLASS_ID);\n}\n\nRuler::~Ruler ()\n{\n   DBG_OBJ_DELETE ();\n}\n\nvoid Ruler::sizeRequestSimpl (core::Requisition *requisition)\n{\n   requisition->width = lout::misc::max (getAvailWidth (true), boxDiffWidth ());\n   requisition->ascent = boxOffsetY ();\n   requisition->descent = boxRestHeight ();\n}\n\nvoid Ruler::getExtremesSimpl (core::Extremes *extremes)\n{\n   extremes->minWidth = extremes->maxWidth = boxDiffWidth ();\n   extremes->minWidthIntrinsic = extremes->minWidth;\n   extremes->maxWidthIntrinsic = extremes->maxWidth;\n   correctExtremes (extremes, false);\n   extremes->adjustmentWidth =\n      lout::misc::min (extremes->minWidthIntrinsic, extremes->minWidth);\n}\n\nbool Ruler::isBlockLevel ()\n{\n   return true;\n}\n\nvoid Ruler::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n   // Nothing to do.\n   DBG_OBJ_LEAVE ();\n}\n\nbool Ruler::usesAvailWidth ()\n{\n   return true;\n}\n\nvoid Ruler::draw (core::View *view, core::Rectangle *area,\n                  core::DrawingContext *context)\n{\n   drawWidgetBox (view, area, false);\n}\n\ncore::Widget *Ruler::getWidgetAtPoint (int x, int y,\n                                       core::GettingWidgetAtPointContext\n                                       *context)\n{\n   // Override (complex) implementation OOFAwareWidget::getWidgetAtPoint().\n\n   if (inAllocation (x, y))\n      return this;\n   else\n      return NULL;\n}\n\ncore::Iterator *Ruler::iterator (core::Content::Type mask, bool atEnd)\n{\n   /** \\todo TextIterator? */\n   return new core::EmptyIterator (this, mask, atEnd);\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/ruler.hh",
    "content": "#ifndef __RULER_HH__\n#define __RULER_HH__\n\n#include \"regardingborder.hh\"\n\nnamespace dw {\n\n/**\n * \\brief Widget for drawing (horizontal) rules.\n *\n * This is really an empty widget, the HTML parser puts a border\n * around it, and drawing is done in dw::core::Widget::drawWidgetBox.\n *\n * Ruler implements RegardingBorder; this way, it is simpler to fit\n * the ruler exactly within the space between floats. Currently, the\n * drawn area of the ruler is too large (but most of the superfluous\n * part is hidden by the floats); this problem will soon solved here\n * in the \"dillo_grows\" repository.\n */\nclass Ruler: public RegardingBorder\n{\nprotected:\n   void sizeRequestSimpl (core::Requisition *requisition);\n   void getExtremesSimpl (core::Extremes *extremes);\n   void containerSizeChangedForChildren ();\n   bool usesAvailWidth ();\n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n   core::Widget *getWidgetAtPoint (int x, int y,\n                                   core::GettingWidgetAtPointContext *context);\n\npublic:\n   static int CLASS_ID;\n\n   Ruler ();\n   ~Ruler ();\n\n   bool isBlockLevel ();\n\n   core::Iterator *iterator (core::Content::Type mask, bool atEnd);\n};\n\n} // namespace dw\n\n#endif // __RULER_HH__\n"
  },
  {
    "path": "dw/selection.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"core.hh\"\n#include \"../lout/debug.hh\"\n\n#include <string.h>\n\nusing namespace lout;\n\n/*\n * strndup() is a GNU extension.\n */\nextern \"C\" char *strndup(const char *s, size_t size)\n{\n   char *r = (char *) malloc (size + 1);\n\n   if (r) {\n      strncpy (r, s, size);\n      r[size] = 0;\n   }\n\n   return r;\n}\n\nnamespace dw {\nnamespace core {\n\nSelectionState::SelectionState ()\n{\n   DBG_OBJ_CREATE (\"dw::core::SelectionState\");\n\n   layout = NULL;\n\n   selectionState = NONE;\n   from = NULL;\n   to = NULL;\n\n   linkState = LINK_NONE;\n   link = NULL;\n}\n\nSelectionState::~SelectionState ()\n{\n   reset ();\n   DBG_OBJ_DELETE ();\n}\n\nvoid SelectionState::reset ()\n{\n   resetSelection ();\n   resetLink ();\n}\n\nvoid SelectionState::resetSelection ()\n{\n   if (from)\n      delete from;\n   from = NULL;\n   if (to)\n      delete to;\n   to = NULL;\n   selectionState = NONE;\n}\n\n\nvoid SelectionState::resetLink ()\n{\n   if (link)\n      delete link;\n   link = NULL;\n   linkState = LINK_NONE;\n}\n\nbool SelectionState::buttonPress (Iterator *it, int charPos, int linkNo,\n                                  EventButton *event)\n{\n   DBG_IF_RTFL {\n      misc::StringBuffer sb;\n      it->intoStringBuffer (&sb);\n      DBG_OBJ_ENTER (\"events\", 0, \"buttonPress\", \"[%s], %d, %d, ...\",\n                     sb.getChars (), charPos, linkNo);\n   }\n\n   Widget *itWidget = it->getWidget ();\n   bool ret = false;\n\n   if (event) {\n      if (event->button == 3) {\n         // menu popup\n         layout->emitLinkPress (itWidget, linkNo, -1, -1, -1, event);\n         ret = true;\n      } else if (linkNo != -1) {\n         // link handling\n         (void) layout->emitLinkPress (itWidget, linkNo, -1, -1, -1, event);\n         resetLink ();\n         linkState = LINK_PRESSED;\n         linkButton = event->button;\n         DeepIterator *newLink = new DeepIterator (it);\n         if (newLink->isEmpty ()) {\n            delete newLink;\n            resetLink ();\n         } else {\n            link = newLink;\n            // It may be that the user has pressed on something activatable\n            // (linkNo != -1), but there is no contents, e.g. with images\n            // without ALTernative text.\n            if (link) {\n               linkChar = correctCharPos (link, charPos);\n               linkNumber = linkNo;\n            }\n         }\n         // We do not return the value of the signal method,\n         // but we do actually process this event.\n         ret = true;\n      } else if (event->button == 1) {\n         // normal selection handling\n         highlight (false, 0);\n         resetSelection ();\n\n         selectionState = SELECTING;\n         DeepIterator *newFrom = new DeepIterator (it);\n         if (newFrom->isEmpty ()) {\n            delete newFrom;\n            resetSelection ();\n         } else {\n            from = newFrom;\n            fromChar = correctCharPos (from, charPos);\n            to = from->cloneDeepIterator ();\n            toChar = correctCharPos (to, charPos);\n         }\n         ret = true;\n      }\n   }\n\n   DBG_OBJ_MSGF (\"events\", 1, \"=> %s\", ret ? \"true\" : \"false\");\n   DBG_OBJ_LEAVE ();\n   return ret;\n}\n\nbool SelectionState::buttonRelease (Iterator *it, int charPos, int linkNo,\n                                    EventButton *event)\n{\n   DBG_IF_RTFL {\n      misc::StringBuffer sb;\n      it->intoStringBuffer (&sb);\n      DBG_OBJ_ENTER (\"events\", 0, \"buttonRelease\", \"[%s], %d, %d, ...\",\n                     sb.getChars (), charPos, linkNo);\n   }\n\n   Widget *itWidget = it->getWidget ();\n   bool ret = false;\n\n   if (linkState == LINK_PRESSED && event && event->button == linkButton) {\n      // link handling\n      ret = true;\n      if (linkNo != -1)\n         (void) layout->emitLinkRelease (itWidget, linkNo, -1, -1, -1, event);\n\n      // The link where the user clicked the mouse button?\n      if (linkNo == linkNumber) {\n         resetLink ();\n         (void) layout->emitLinkClick (itWidget, linkNo, -1, -1, -1, event);\n      } else {\n         if (event->button == 1)\n            // Reset links and switch to selection mode. The selection\n            // state will be set to SELECTING, which is handled some lines\n            // below.\n            switchLinkToSelection (it, charPos);\n      }\n   }\n\n   if (selectionState == SELECTING && event && event->button == 1) {\n      // normal selection\n      ret = true;\n      adjustSelection (it, charPos);\n\n      if (from->compareTo (to) == 0 && fromChar == toChar)\n         // nothing selected\n         resetSelection ();\n      else {\n         copy ();\n         selectionState = SELECTED;\n      }\n   }\n\n   DBG_OBJ_MSGF (\"events\", 1, \"=> %s\", ret ? \"true\" : \"false\");\n   DBG_OBJ_LEAVE ();\n   return ret;\n}\n\nbool SelectionState::buttonMotion (Iterator *it, int charPos, int linkNo,\n                                   EventMotion *event)\n{\n   if (linkState == LINK_PRESSED) {\n      //link handling\n      if (linkNo != linkNumber)\n         // No longer the link where the user clicked the mouse button.\n         // Reset links and switch to selection mode.\n         switchLinkToSelection (it, charPos);\n      // Still in link: do nothing.\n   } else if (selectionState == SELECTING) {\n      // selection\n      adjustSelection (it, charPos);\n   }\n\n   return true;\n}\n\n/**\n * \\brief General form of dw::core::SelectionState::buttonPress,\n *    dw::core::SelectionState::buttonRelease and\n *    dw::core::SelectionState::buttonMotion.\n */\nbool SelectionState::handleEvent (EventType eventType, Iterator *it,\n                                  int charPos, int linkNo,\n                                  MousePositionEvent *event)\n{\n   switch (eventType) {\n   case BUTTON_PRESS:\n      return buttonPress (it, charPos, linkNo, (EventButton*)event);\n\n   case BUTTON_RELEASE:\n      return buttonRelease (it, charPos, linkNo, (EventButton*)event);\n\n   case BUTTON_MOTION:\n      return buttonMotion (it, charPos, linkNo, (EventMotion*)event);\n\n\n   default:\n      misc::assertNotReached ();\n   }\n\n   return false;\n}\n\n\n/**\n * \\brief This method is called when the user decides not to activate a link,\n *    but instead select text.\n */\nvoid SelectionState::switchLinkToSelection (Iterator *it, int charPos)\n{\n   // It may be that selection->link is NULL, see a_Selection_button_press.\n   if (link) {\n      // Reset old selection.\n      highlight (false, 0);\n      resetSelection ();\n\n      // Transfer link state into selection state.\n      from = link->cloneDeepIterator ();\n      fromChar = linkChar;\n      to = from->createVariant (it);\n      toChar = correctCharPos (to, charPos);\n      selectionState = SELECTING;\n\n      // Reset link status.\n      resetLink ();\n\n      highlight (true, 0);\n\n   } else {\n      // A link was pressed on, but there is nothing to select. Reset\n      // everything.\n      resetSelection ();\n      resetLink ();\n   }\n}\n\n/**\n * \\brief This method is used by core::dw::SelectionState::buttonMotion and\n *    core::dw::SelectionState::buttonRelease, and changes the second limit of\n *    the already existing selection region.\n */\nvoid SelectionState::adjustSelection (Iterator *it, int charPos)\n{\n   DeepIterator *newTo;\n   int newToChar, cmpOld, cmpNew, cmpDiff, len;\n   bool bruteHighlighting = false;\n\n   newTo = to->createVariant (it);\n   newToChar = correctCharPos (newTo, charPos);\n\n   cmpOld = to->compareTo (from);\n   cmpNew = newTo->compareTo (from);\n\n   if (cmpOld == 0 || cmpNew == 0) {\n      // Either before, or now, the limits differ only by the character\n      // position.\n      bruteHighlighting = true;\n   } else if (cmpOld * cmpNew < 0) {\n      // The selection order has changed, i.e. the user moved the selection\n      // end again beyond the position he started.\n      bruteHighlighting = true;\n   } else {\n      // Here, cmpOld and cmpNew are equivalent and != 0.\n      cmpDiff = newTo->compareTo (to);\n\n      if (cmpOld * cmpDiff > 0) {\n         // The user has enlarged the selection. Highlight the difference.\n         if (cmpDiff < 0) {\n            len = correctCharPos (to, END_OF_WORD);\n            highlight0 (true, newTo, newToChar, to, len + 1, 1);\n         } else {\n            highlight0 (true, to, 0, newTo, newToChar, -1);\n         }\n      } else {\n         if (cmpOld * cmpDiff < 0) {\n            // The user has reduced the selection. Unhighlight the difference.\n            highlight0 (false, to, 0, newTo, 0, cmpDiff);\n         }\n\n         // Otherwise, the user has changed the position only slightly.\n         // In both cases, re-highlight the new position.\n         if (cmpOld < 0) {\n            len = correctCharPos (newTo, END_OF_WORD);\n            newTo->highlight (newToChar, len + 1, HIGHLIGHT_SELECTION);\n         } else\n            newTo->highlight (0, newToChar, HIGHLIGHT_SELECTION);\n      }\n   }\n\n   if (bruteHighlighting)\n      highlight (false, 0);\n\n   delete to;\n   to = newTo;\n   toChar = newToChar;\n\n   if (bruteHighlighting)\n      highlight (true, 0);\n}\n\n/**\n * \\brief This method deals especially with the case that a widget passes\n *    dw::core::SelectionState::END_OF_WORD.\n */\nint SelectionState::correctCharPos (DeepIterator *it, int charPos)\n{\n   Iterator *top = it->getTopIterator ();\n   int len;\n\n   if (top->getContent()->type == Content::TEXT)\n      len = strlen(top->getContent()->text);\n   else\n      len = 1;\n\n   return misc::min(charPos, len);\n}\n\nvoid SelectionState::highlight0 (bool fl, DeepIterator *from, int fromChar,\n                                 DeepIterator *to, int toChar, int dir)\n{\n   DeepIterator *a, *b, *i;\n   int cmp, aChar, bChar;\n   bool start;\n\n   if (from && to) {\n      cmp = from->compareTo (to);\n      if (cmp == 0) {\n         if (fl) {\n            if (fromChar < toChar)\n               from->highlight (fromChar, toChar, HIGHLIGHT_SELECTION);\n            else\n               from->highlight (toChar, fromChar, HIGHLIGHT_SELECTION);\n         } else\n            from->unhighlight (0, HIGHLIGHT_SELECTION);\n         return;\n      }\n\n      if (cmp < 0) {\n         a = from;\n         aChar = fromChar;\n         b = to;\n         bChar = toChar;\n      } else {\n         a = to;\n         aChar = toChar;\n         b = from;\n         bChar = fromChar;\n      }\n\n      for (i = a->cloneDeepIterator (), start = true;\n           (cmp = i->compareTo (b)) <= 0;\n           i->next (), start = false) {\n         if (i->getContent()->type == Content::TEXT) {\n            if (fl) {\n               if (start) {\n                  i->highlight (aChar, strlen (i->getContent()->text) + 1,\n                                HIGHLIGHT_SELECTION);\n               } else if (cmp == 0) {\n                  // the end\n                  i->highlight (0, bChar, HIGHLIGHT_SELECTION);\n               } else {\n                  i->highlight (0, strlen (i->getContent()->text) + 1,\n                                HIGHLIGHT_SELECTION);\n               }\n            } else {\n               i->unhighlight (dir, HIGHLIGHT_SELECTION);\n            }\n         }\n      }\n      delete i;\n   }\n}\n\nvoid SelectionState::copy()\n{\n   if (from && to) {\n      Iterator *si;\n      DeepIterator *a, *b, *i;\n      int cmp, aChar, bChar;\n      bool start;\n      char *tmp;\n      misc::StringBuffer strbuf;\n\n      cmp = from->compareTo (to);\n      if (cmp == 0) {\n         if (from->getContent()->type == Content::TEXT) {\n            si = from->getTopIterator ();\n            if (fromChar < toChar)\n               tmp = strndup (si->getContent()->text + fromChar,\n                              toChar - fromChar);\n            else\n               tmp = strndup (si->getContent()->text + toChar,\n                              fromChar - toChar);\n            strbuf.appendNoCopy (tmp);\n         }\n      } else {\n         if (cmp < 0) {\n            a = from;\n            aChar = fromChar;\n            b = to;\n            bChar = toChar;\n         } else {\n            a = to;\n            aChar = toChar;\n            b = from;\n            bChar = fromChar;\n         }\n\n         for (i = a->cloneDeepIterator (), start = true;\n              (cmp = i->compareTo (b)) <= 0;\n              i->next (), start = false) {\n            si = i->getTopIterator ();\n            switch (si->getContent()->type) {\n            case Content::TEXT:\n               if (start) {\n                  tmp = strndup (si->getContent()->text + aChar,\n                                 strlen (i->getContent()->text) - aChar);\n                  strbuf.appendNoCopy (tmp);\n               } else if (cmp == 0) {\n                  // the end\n                  tmp = strndup (si->getContent()->text, bChar);\n                  strbuf.appendNoCopy (tmp);\n               } else\n                  strbuf.append (si->getContent()->text);\n\n               if (si->getContent()->space && cmp != 0)\n                  strbuf.append (\" \");\n\n               break;\n\n            case Content::BREAK:\n               if (si->getContent()->breakSpace > 0)\n                  strbuf.append (\"\\n\\n\");\n               else\n                  strbuf.append (\"\\n\");\n               break;\n            default:\n               // Make pedantic compilers happy. Especially\n               // DW_CONTENT_WIDGET is never returned by a DwDeepIterator.\n               break;\n            }\n         }\n         delete i;\n      }\n\n      layout->copySelection(strbuf.getChars());\n   }\n}\n\n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/selection.hh",
    "content": "#ifndef __DW_SELECTION_H__\n#define __DW_SELECTION_H__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief This class handles selections, as well as activation of links,\n *    which is closely related.\n *\n * <h3>General Overview</h3>\n *\n * dw::core::SelectionState is associated with dw::core::Layout. The selection\n * state is controlled by \"abstract events\", which are sent by single\n * widgets by calling one of the following methods:\n *\n * <ul>\n * <li> dw::core::SelectionState::buttonPress for button press events,\n * <li> dw::core::SelectionState::buttonRelease for button release events, and\n * <li> dw::core::SelectionState::buttonMotion for motion events (with pressed\n *      mouse button).\n * </ul>\n *\n * The widget must construct simple iterators (dw::core::Iterator), which will\n * be transferred to deep iterators (dw::core::DeepIterator), see below for\n * more details. All event handling methods have the same signature, the\n * arguments in detail are:\n *\n * <table>\n * <tr><td>dw::core::Iterator *it       <td>the iterator pointing on the item\n *                                          under the mouse pointer; this\n *                                          iterator \\em must be created with\n *                                         dw::core::Content::SELECTION_CONTENT\n *                                          as mask\n * <tr><td>int charPos                  <td>the exact (character) position\n *                                          within the iterator,\n * <tr><td>int linkNo                   <td>if this item is associated with a\n *                                          link, its number (see\n *                                          dw::core::Layout::LinkReceiver),\n *                                          otherwise -1\n * <tr><td>dw::core::EventButton *event <td>the event itself; only the button\n *                                          is used\n * </table>\n *\n * Look also at dw::core::SelectionState::handleEvent, which may be useful\n * in some circumstances.\n *\n * In some cases, \\em charPos would be difficult to determine. E.g., when\n * the dw::Textblock widget decides that the user is pointing on a position\n * <i>at the end</i> of an image (DwImage), it constructs a simple iterator\n * pointing on this image widget. In a simple iterator, that fact that\n * the pointer is at the end, would be represented by \\em charPos == 1. But\n * when transferring this simple iterator into an deep iterator, this\n * simple iterator is discarded and instead the stack has an iterator\n * pointing to text at the top. As a result, only the first letter of the\n * ALT text would be copied.\n *\n * To avoid this problem, widgets should in this case pass\n * dw::core::SelectionState::END_OF_WORD as \\em charPos, which is then\n * automatically reduced to the actual length of the deep(!) iterator.\n *\n * The return value is the same as in DwWidget event handling methods.\n * I.e., in most cases, they should simply return it. The events\n * dw::core::Layout::LinkReceiver::press,\n * dw::core::Layout::LinkReceiver::release and\n * dw::core::Layout::LinkReceiver::click (but not\n * dw::core::Layout::LinkReceiver::enter) are emitted by these methods, so\n * that widgets which let dw::core::SelectionState handle links, should only\n * emit dw::core::Layout::LinkReceiver::enter for themselves.\n *\n * <h3>Selection State</h3>\n *\n * Selection interferes with handling the activation of links, so the\n * latter is also handled by the dw::core::SelectionState. Details are based on\n * following guidelines:\n *\n * <ol>\n * <li> It should be simple to select links and to start selection in\n *      links. The rule to distinguish between link activation and\n *      selection is that the selection starts as soon as the user leaves\n *      the link. (This is, IMO, a useful feature. Even after drag and\n *      drop has been implemented in dillo, this should be somehow\n *      preserved.)\n *\n * <li> The selection should stay as long as possible, i.e., the old\n *      selection is only cleared when a new selection is started.\n * </ol>\n *\n * The latter leads to a model with two states: the selection state and\n * the link handling state.\n *\n * The general selection works, for events not pointing on links, like\n * this (numbers in parantheses after the event denote the button, \"n\"\n * means arbitrary button):\n *\n * \\dot\n * digraph G {\n *    node [shape=ellipse, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"open\", labelfontname=Helvetica, labelfontsize=10,\n *          color=\"#404040\", labelfontcolor=\"#000080\",\n *          fontname=Helvetica, fontsize=10, fontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *\n *    NONE;\n *    SELECTING;\n *    q [label=\"Anything selected?\", shape=plaintext];\n *    SELECTED;\n *\n *    NONE -> SELECTING [label=\"press(1)\\non non-link\"];\n *    SELECTING -> SELECTING [label=\"motion(1)\"];\n *    SELECTING -> q [label=\"release(1)\"];\n *    q -> SELECTED [label=\"yes\"];\n *    q -> NONE [label=\"no\"];\n *    SELECTED -> SELECTING [label=\"press(1)\"];\n *\n * }\n * \\enddot\n *\n * The selected region is represented by two instances of\n * dw::core::DeepIterator.\n *\n * Links are handled by a different state machine:\n *\n * \\dot\n * digraph G {\n *    node [shape=ellipse, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"open\", labelfontname=Helvetica, labelfontsize=10,\n *          color=\"#404040\", labelfontcolor=\"#000080\",\n *          fontname=Helvetica, fontsize=10, fontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *\n *    LINK_NONE;\n *    LINK_PRESSED;\n *    click [label=\"Emit \\\"click\\\" signal.\", shape=record];\n *    q11 [label=\"Still the same link?\", shape=plaintext];\n *    q21 [label=\"Still the same link?\", shape=plaintext];\n *    q22 [label=\"n == 1?\", shape=plaintext];\n *    SELECTED [label=\"Switch selection\\nto SELECTED\", shape=record];\n *    q12 [label=\"n == 1?\", shape=plaintext];\n *    SELECTING [label=\"Switch selection\\nto SELECTING\", shape=record];\n *\n *    LINK_NONE -> LINK_PRESSED [label=\"press(n)\\non link\"];\n *    LINK_PRESSED -> q11 [label=\"motion(n)\"];\n *    q11 -> LINK_PRESSED [label=\"yes\"];\n *    q11 -> q12 [label=\"no\"];\n *    q12 -> SELECTING [label=\"yes\"];\n *    SELECTING -> LINK_NONE;\n *    q12 -> LINK_NONE [label=\"no\"];\n *    LINK_PRESSED -> q21 [label=\"release(n)\"];\n *    q21 -> click [label=\"yes\"];\n *    click -> LINK_NONE;\n *    q21 -> q22 [label=\"no\"];\n *    q22 -> SELECTED [label=\"yes\"];\n *    SELECTED -> LINK_NONE;\n *    q22 -> LINK_NONE [label=\"no\"];\n * }\n * \\enddot\n *\n * Switching selection simply means that the selection state will\n * eventually be SELECTED/SELECTING, with the original and the current\n * position making up the selection region. This happens for button 1,\n * events with buttons other than 1 do not affect selection at all.\n *\n *\n * \\todo dw::core::SelectionState::buttonMotion currently always assumes\n *    that button 1 has been pressed (since otherwise it would not do\n *    anything). This should be made a bit cleaner.\n *\n * \\todo The selection should be cleared, when the user selects something\n *    somewhere else (perhaps switched into \"non-active\" mode, as e.g. Gtk+\n *    does).\n *\n */\nclass SelectionState\n{\npublic:\n   enum { END_OF_WORD = 1 << 30 };\n\nprivate:\n   Layout *layout;\n\n   // selection\n   enum {\n      NONE,\n      SELECTING,\n      SELECTED\n   } selectionState;\n\n   DeepIterator *from, *to;\n   int fromChar, toChar;\n\n   // link handling\n   enum {\n      LINK_NONE,\n      LINK_PRESSED\n   } linkState;\n\n   int linkButton;\n   DeepIterator *link;\n   int linkChar, linkNumber;\n\n   void resetSelection ();\n   void resetLink ();\n   void switchLinkToSelection (Iterator *it, int charPos);\n   void adjustSelection (Iterator *it, int charPos);\n   static int correctCharPos (DeepIterator *it, int charPos);\n\n   void highlight (bool fl, int dir)\n   { highlight0 (fl, from, fromChar, to, toChar, dir); }\n\n   void highlight0 (bool fl, DeepIterator *from, int fromChar,\n                    DeepIterator *to, int toChar, int dir);\n   void copy ();\n\npublic:\n   enum EventType { BUTTON_PRESS, BUTTON_RELEASE, BUTTON_MOTION };\n\n   SelectionState ();\n   ~SelectionState ();\n\n   inline void setLayout (Layout *layout) { this->layout = layout; }\n   void reset ();\n   bool buttonPress (Iterator *it, int charPos, int linkNo,\n                     EventButton *event);\n   bool buttonRelease (Iterator *it, int charPos, int linkNo,\n                       EventButton *event);\n   bool buttonMotion (Iterator *it, int charPos, int linkNo,\n                      EventMotion *event);\n\n   bool handleEvent (EventType eventType, Iterator *it, int charPos,\n                     int linkNo, MousePositionEvent *event);\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_SELECTION_H__\n"
  },
  {
    "path": "dw/simpletablecell.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"simpletablecell.hh\"\n#include \"tablecell.hh\"\n#include \"../lout/debug.hh\"\n\nusing namespace lout;\n\nnamespace dw {\n\nint SimpleTableCell::CLASS_ID = -1;\n\nSimpleTableCell::SimpleTableCell (bool limitTextWidth):\n   Textblock (limitTextWidth)\n{\n   DBG_OBJ_CREATE (\"dw::SimpleTableCell\");\n   registerName (\"dw::SimpleTableCell\", &CLASS_ID);\n}\n\nSimpleTableCell::~SimpleTableCell()\n{\n   DBG_OBJ_DELETE ();\n}\n\nbool SimpleTableCell::getAdjustMinWidth ()\n{\n   return tablecell::getAdjustMinWidth ();\n}\n\nbool SimpleTableCell::isBlockLevel ()\n{\n   return tablecell::isBlockLevel ();\n}\n\nbool SimpleTableCell::usesMaxGeneratorWidth ()\n{\n   return tablecell::usesMaxGeneratorWidth ();\n}\n\nint SimpleTableCell::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"SimpleTableCell::getAvailWidthOfChild\",\n                  \"%p, %s\", child, forceValue ? \"true\" : \"false\");\n\n   int width = tablecell::correctAvailWidthOfChild\n      (this, child, Textblock::getAvailWidthOfChild (child, forceValue),\n       forceValue);\n\n   DBG_OBJ_LEAVE ();\n   return width;\n}\n\nint SimpleTableCell::getAvailHeightOfChild (Widget *child, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"SimpleTableCell::getAvailHeightOfChild\",\n                  \"%p, %s\", child, forceValue ? \"true\" : \"false\");\n\n   int height = tablecell::correctAvailHeightOfChild\n      (this, child, Textblock::getAvailHeightOfChild (child, forceValue),\n       forceValue);\n\n   DBG_OBJ_LEAVE ();\n   return height;\n}\n\nvoid SimpleTableCell::correctRequisitionOfChild (Widget *child,\n                                                 core::Requisition *requisition,\n                                                 void (*splitHeightFun) (int,\n                                                                         int*,\n                                                                         int*),\n                                                 bool allowDecreaseWidth,\n                                                 bool allowDecreaseHeight)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"SimpleTableCell::correctRequisitionOfChild\",\n                  \"%p, %d * (%d + %d), ..., %s, %s\", child, requisition->width,\n                  requisition->ascent, requisition->descent,\n                  misc::boolToStr (allowDecreaseWidth),\n                  misc::boolToStr (allowDecreaseHeight));\n\n   Textblock::correctRequisitionOfChild (child, requisition, splitHeightFun,\n                                         allowDecreaseWidth,\n                                         allowDecreaseHeight);\n   tablecell::correctCorrectedRequisitionOfChild (this, child, requisition,\n                                                  splitHeightFun,\n                                                  allowDecreaseWidth,\n                                                  allowDecreaseHeight);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid SimpleTableCell::correctExtremesOfChild (Widget *child,\n                                              core::Extremes *extremes,\n                                              bool useAdjustmentWidth)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"SimpleTableCell::correctExtremesOfChild\",\n                  \"%p, %d (%d) / %d (%d)\",\n                  child, extremes->minWidth, extremes->minWidthIntrinsic,\n                  extremes->maxWidth, extremes->maxWidthIntrinsic);\n\n   Textblock::correctExtremesOfChild (child, extremes, useAdjustmentWidth); \n   tablecell::correctCorrectedExtremesOfChild (this, child, extremes,\n                                               useAdjustmentWidth);\n\n   DBG_OBJ_LEAVE ();\n}\n\nint SimpleTableCell::applyPerWidth (int containerWidth,\n                                    core::style::Length perWidth)\n{\n   return tablecell::applyPerWidth (this, containerWidth, perWidth);\n}\n\nint SimpleTableCell::applyPerHeight (int containerHeight,\n                                     core::style::Length perHeight)\n{\n   return tablecell::applyPerHeight (this, containerHeight, perHeight);\n}\n\nbool SimpleTableCell::adjustExtraSpaceWhenCorrectingRequisitionByOOF ()\n{\n   return tablecell::adjustExtraSpaceWhenCorrectingRequisitionByOOF ();\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/simpletablecell.hh",
    "content": "#ifndef __DW_SIMPLETABLECELL_HH__\n#define __DW_SIMPLETABLECELL_HH__\n\n#include \"textblock.hh\"\n\nnamespace dw {\n\nclass SimpleTableCell: public Textblock\n{\nprotected:\n   int getAvailWidthOfChild (Widget *child, bool forceValue);\n   int getAvailHeightOfChild (Widget *child, bool forceValue);\n\n   void correctRequisitionOfChild (Widget *child,\n                                   core::Requisition *requisition,\n                                   void (*splitHeightFun) (int, int*, int*),\n                                   bool allowDecreaseWidth,\n                                   bool allowDecreaseHeight);\n   void correctExtremesOfChild (Widget *child, core::Extremes *extremes,\n                                bool useAdjustmentWidth);\n\n   bool getAdjustMinWidth ();\n\n   bool adjustExtraSpaceWhenCorrectingRequisitionByOOF ();\n\npublic:\n   static int CLASS_ID;\n\n   SimpleTableCell (bool limitTextWidth);\n   ~SimpleTableCell ();\n\n   int applyPerWidth (int containerWidth, core::style::Length perWidth);\n   int applyPerHeight (int containerHeight, core::style::Length perHeight);\n\n   bool isBlockLevel ();\n\n   bool usesMaxGeneratorWidth ();\n};\n\n} // namespace dw\n\n#endif // __DW_SIMPLETABLECELL_HH__\n"
  },
  {
    "path": "dw/stackingcontextmgr.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"core.hh\"\n#include \"../lout/debug.hh\"\n\nusing namespace lout::misc;\nusing namespace lout::container::typed;\n\nnamespace dw {\n\nnamespace core {\n\nStackingContextMgr::StackingContextMgr (Widget *widget)\n{\n   DBG_OBJ_CREATE (\"dw::core::StackingContextMgr\");\n\n   this->widget = widget;\n\n   childSCWidgets = new Vector<Widget> (1, false);\n   DBG_OBJ_SET_NUM (\"childSCWidgets.size\", childSCWidgets->size());\n\n   numZIndices = 0;\n   zIndices = NULL;\n   DBG_OBJ_SET_NUM (\"numZIndices\", numZIndices);\n}\n\nStackingContextMgr::~StackingContextMgr ()\n{\n   delete childSCWidgets;\n   if (zIndices)\n      free (zIndices);\n   DBG_OBJ_DELETE ();\n}\n\nvoid StackingContextMgr::addChildSCWidget (Widget *widget)\n{\n   DBG_OBJ_ENTER (\"common.scm\", 0, \"addChildSCWidget\", \"%p [z-index = %d]\",\n                  widget, widget->getStyle()->zIndex);\n\n   int pos = findZIndex (widget->getStyle()->zIndex, true);\n   DBG_OBJ_MSGF (\"common.scm\", 1, \"pos = %d\", pos);\n   if (pos == -1) {\n      pos = findZIndex (widget->getStyle()->zIndex, false);\n      DBG_OBJ_MSGF (\"common.scm\", 1, \"pos = %d\", pos);\n\n      numZIndices++;\n      DBG_OBJ_SET_NUM (\"numZIndices\", numZIndices);\n      zIndices = (int*)(zIndices ?\n                        realloc (zIndices, numZIndices * sizeof (int)) :\n                        malloc (numZIndices * sizeof (int)));\n\n      for (int i = numZIndices - 1; i >= pos + 1; i--) {\n         zIndices[i] = zIndices[i - 1];\n         DBG_OBJ_ARRSET_NUM (\"zIndex\", i, zIndices[i]);\n      }\n\n      zIndices[pos] = widget->getStyle()->zIndex;\n      DBG_OBJ_ARRSET_NUM (\"zIndex\", pos, zIndices[pos]);\n   }      \n\n   childSCWidgets->put (widget);\n   DBG_OBJ_SET_NUM (\"childSCWidgets.size\", childSCWidgets->size());\n   DBG_OBJ_ARRSET_PTR (\"childSCWidgets\", childSCWidgets->size() - 1, widget);\n\n   DBG_OBJ_LEAVE ();\n}\n\nint StackingContextMgr::findZIndex (int zIndex, bool mustExist)\n{\n   int result = -123; // Compiler happiness: GCC 4.7 does not handle this?\n\n   if (numZIndices == 0)\n      result = mustExist ? -1 : 0;\n   else {\n      int low = 0, high = numZIndices - 1;\n      bool found = false;\n   \n      while (!found) {\n         int index = (low + high) / 2;\n         if (zIndex == zIndices[index]) {\n            found = true;\n            result = index;\n         } else {\n            if (low >= high) {\n               if (mustExist) {\n                  found = true;\n                  result = -1;\n               } else {\n                  found = true;\n                  result = zIndex > zIndices[index] ? index + 1 : index;\n               }\n            }\n            \n            if (zIndex < zIndices[index])\n               high = index - 1;\n            else\n               low = index + 1;\n         }\n      }\n   }\n\n   return result;\n}\n\nvoid StackingContextMgr::draw (View *view, Rectangle *area, int startZIndex,\n                               int endZIndex, DrawingContext *context)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"draw\", \"[%d, %d, %d * %d], %d, %d\",\n                  area->x, area->y, area->width, area->height, startZIndex,\n                  endZIndex);\n\n   for (int zIndexIndex = 0; zIndexIndex < numZIndices; zIndexIndex++) {\n      // Wrong region of z-indices (top or bottom) is simply ignored\n      // (as well as non-defined zIndices).\n      if (zIndices != NULL && zIndices[zIndexIndex] >= startZIndex &&\n          zIndices[zIndexIndex] <= endZIndex) {\n         DBG_OBJ_MSGF (\"draw\", 1, \"drawing zIndex = %d\", zIndices[zIndexIndex]);\n         DBG_OBJ_MSG_START ();\n\n         for (int i = 0; i < childSCWidgets->size (); i++) {\n            Widget *child = childSCWidgets->get (i);\n            DBG_OBJ_MSGF (\"draw\", 2, \"widget %p has zIndex = %d\",\n                          child, child->getStyle()->zIndex);\n\n            Rectangle childArea;\n            if (child->getStyle()->zIndex == zIndices[zIndexIndex] &&\n                child->intersects (widget, area, &childArea))\n               child->draw (view, &childArea, context);\n         }\n\n         DBG_OBJ_MSG_END ();\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nWidget *StackingContextMgr::getWidgetAtPoint (int x, int y,\n                                              GettingWidgetAtPointContext\n                                              *context,\n                                              int startZIndex, int endZIndex)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"getWidgetAtPoint\", \"%d, %d\", x, y);\n\n   Widget *widgetAtPoint = NULL;\n\n   for (int zIndexIndex = numZIndices - 1;\n        widgetAtPoint == NULL && zIndexIndex >= 0; zIndexIndex--) {\n      // Wrong region of z-indices (top or bottom) is simply ignored\n      // (as well as non-defined zIndices).\n      if (zIndices != NULL && zIndices[zIndexIndex] >= startZIndex &&\n          zIndices[zIndexIndex] <= endZIndex) {\n         DBG_OBJ_MSGF (\"events\", 1, \"searching zIndex = %d\",\n                       zIndices[zIndexIndex]);\n         DBG_OBJ_MSG_START ();\n\n         for (int i = childSCWidgets->size () - 1;\n              widgetAtPoint == NULL && i >= 0; i--) {\n            Widget *child = childSCWidgets->get (i);\n            DBG_OBJ_MSGF (\"events\", 2, \"widget %p has zIndex = %d\",\n                          child, child->getStyle()->zIndex);\n            if (child->getStyle()->zIndex == zIndices[zIndexIndex])\n               widgetAtPoint = child->getWidgetAtPoint (x, y, context);\n         }\n         \n         DBG_OBJ_MSG_END ();\n      }\n   }\n\n   DBG_OBJ_MSGF (\"events\", 0, \"=> %p\", widgetAtPoint);\n   DBG_OBJ_LEAVE ();\n   return widgetAtPoint;\n}\n\n} // namespace core\n\n} // namespace dw\n"
  },
  {
    "path": "dw/stackingcontextmgr.hh",
    "content": "#ifndef __DW_STACKINGCONTEXTMGR_HH__\n#define __DW_STACKINGCONTEXTMGR_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\n#include \"../lout/container.hh\"\n\n#include <limits.h>\n\nnamespace dw {\n\nnamespace core {\n\n/**\n * \\brief See \\ref dw-stacking-context.\n */\nclass StackingContextMgr\n{\nprivate:\n   Widget *widget;\n   lout::container::typed::Vector<Widget> *childSCWidgets;\n   int *zIndices, numZIndices;\n\n   int findZIndex (int zIndex, bool mustExist);\n   void draw (View *view, Rectangle *area, int startZIndex, int endZIndex,\n              DrawingContext *context);\n   Widget *getWidgetAtPoint (int x, int y,\n                             core::GettingWidgetAtPointContext *context,\n                             int startZIndex, int endZIndex);\n\npublic:\n   StackingContextMgr (Widget *widget);\n   ~StackingContextMgr ();\n\n   inline static bool isEstablishingStackingContext (Widget *widget) {\n      return IMPL_POS &&\n         widget->getStyle()->position != style::POSITION_STATIC &&\n         widget->getStyle()->zIndex != style::Z_INDEX_AUTO;\n   }\n\n   inline static bool handledByStackingContextMgr  (Widget *widget) {\n      // Each widget establishing a stacking context is child of another\n      // stacking context, so drawn by StackingContextMgr::drawTop or\n      // StackingContextMgr::drawBottom etc.\n      return widget->getParent () != NULL\n         && isEstablishingStackingContext (widget);\n   }\n\n   void addChildSCWidget (Widget *widget);\n\n   inline int getNumZIndices () { return numZIndices; }\n   inline int getNumChildSCWidgets () { return childSCWidgets->size (); }\n\n   inline void drawBottom (View *view, Rectangle *area, DrawingContext *context)\n   { draw (view, area, INT_MIN, -1, context); }\n   void drawTop (View *view, Rectangle *area, DrawingContext *context)\n   { draw (view, area, 0, INT_MAX, context); }\n\n   inline Widget *getTopWidgetAtPoint (int x, int y,\n                                       core::GettingWidgetAtPointContext\n                                       *context)\n   { return getWidgetAtPoint (x, y, context, 0, INT_MAX); }\n\n   inline Widget *getBottomWidgetAtPoint (int x, int y,\n                                          core::GettingWidgetAtPointContext\n                                          *context)\n   { return getWidgetAtPoint (x, y, context, INT_MIN, -1); }\n};\n\n} // namespace core\n\n} // namespace dw\n\n#endif // __DW_STACKINGCONTEXTMGR_HH__\n"
  },
  {
    "path": "dw/style.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <unistd.h>\n#include <ctype.h>\n#include <math.h>\n\n#include \"core.hh\"\n#include \"../lout/msg.h\"\n\nusing namespace lout;\n\nnamespace dw {\nnamespace core {\nnamespace style {\n\nconst bool drawBackgroundLineByLine = false;\n\nconst int MIN_BG_IMG_W = 10;\nconst int MIN_BG_IMG_H = 10;\nconst int OPT_BG_IMG_W = 50;\nconst int OPT_BG_IMG_H = 50;\n\nstatic void calcBackgroundRelatedValues (StyleImage *backgroundImage,\n                                         BackgroundRepeat backgroundRepeat,\n                                         BackgroundAttachment\n                                         backgroundAttachment,\n                                         Length backgroundPositionX,\n                                         Length backgroundPositionY,\n                                         int xDraw, int yDraw, int widthDraw,\n                                         int heightDraw, int xRef, int yRef,\n                                         int widthRef, int heightRef,\n                                         bool *repeatX, bool *repeatY,\n                                         int *origX, int *origY,\n                                         int *tileX1, int *tileX2, int *tileY1,\n                                         int *tileY2, bool *doDraw);\n\nvoid StyleAttrs::initValues ()\n{\n   x_link = -1;\n   x_lang[0] = x_lang[1] = 0;\n   x_img = -1;\n   x_tooltip = NULL;\n   textDecoration = TEXT_DECORATION_NONE;\n   textAlign = TEXT_ALIGN_LEFT;\n   textAlignChar = '.';\n   textTransform = TEXT_TRANSFORM_NONE;\n   listStylePosition = LIST_STYLE_POSITION_OUTSIDE;\n   listStyleType = LIST_STYLE_TYPE_DISC;\n   valign = VALIGN_BASELINE;\n   backgroundColor = NULL;\n   backgroundImage = NULL;\n   backgroundRepeat = BACKGROUND_REPEAT;\n   backgroundAttachment = BACKGROUND_ATTACHMENT_SCROLL;\n   backgroundPositionX = createPerLength (0);\n   backgroundPositionY = createPerLength (0);\n   width = height = lineHeight = LENGTH_AUTO;\n   minWidth = maxWidth = minHeight = maxHeight = LENGTH_AUTO;\n   vloat = FLOAT_NONE;\n   clear = CLEAR_NONE;\n   overflow = OVERFLOW_VISIBLE;\n   position = POSITION_STATIC;\n   top = bottom = left = right = LENGTH_AUTO;\n   textIndent = 0;\n   margin.setVal (0);\n   borderWidth.setVal (0);\n   padding.setVal (0);\n   borderCollapse = BORDER_MODEL_SEPARATE;\n   setBorderColor (NULL);\n   setBorderStyle (BORDER_NONE);\n   hBorderSpacing = 0;\n   vBorderSpacing = 0;\n   wordSpacing = 0;\n\n   display = DISPLAY_INLINE;\n   whiteSpace = WHITE_SPACE_NORMAL;\n   cursor = CURSOR_DEFAULT;\n   zIndex = Z_INDEX_AUTO;\n}\n\n/**\n * \\brief Reset those style attributes to their standard values, which are\n *    not inherited, according to CSS.\n */\nvoid StyleAttrs::resetValues ()\n{\n   x_img = -1;\n\n   valign = VALIGN_BASELINE;\n   textAlignChar = '.';\n   vloat = FLOAT_NONE; /** \\todo Correct? Check specification. */\n   clear = CLEAR_NONE; /** \\todo Correct? Check specification. */\n   overflow = OVERFLOW_VISIBLE;\n   position = POSITION_STATIC; /** \\todo Correct? Check specification. */\n   top = bottom = left = right = LENGTH_AUTO; /** \\todo Correct? Check\n                                                  specification. */\n   backgroundColor = NULL;\n   backgroundImage = NULL;\n   backgroundRepeat = BACKGROUND_REPEAT;\n   backgroundAttachment = BACKGROUND_ATTACHMENT_SCROLL;\n   backgroundPositionX = createPerLength (0);\n   backgroundPositionY = createPerLength (0);\n   width = LENGTH_AUTO;\n   height = LENGTH_AUTO;\n   minWidth = maxWidth = minHeight = maxHeight = LENGTH_AUTO;\n\n   margin.setVal (0);\n   borderWidth.setVal (0);\n   padding.setVal (0);\n   setBorderColor (NULL);\n   setBorderStyle (BORDER_NONE);\n   hBorderSpacing = 0;\n   vBorderSpacing = 0;\n\n   display = DISPLAY_INLINE;\n}\n\n/**\n * \\brief This method returns whether something may change its size, when\n *    its style changes from this style to \\em otherStyle.\n *\n * It is mainly for optimizing style changes where only colors etc change\n * (where false would be returned), in some cases it may return true, although\n * a size change does not actually happen (e.g. when in a certain\n * context a particular attribute is ignored).\n *\n * \\todo Should for CSS implemented properly. Currently, size changes are\n * not needed, so always false is returned. See also\n * dw::core::Widget::setStyle.\n */\nbool StyleAttrs::sizeDiffs (StyleAttrs *otherStyle)\n{\n   return false;\n}\n\nbool StyleAttrs::equals (object::Object *other) {\n   StyleAttrs *otherAttrs = (StyleAttrs *) other;\n\n   return this == otherAttrs ||\n      (font == otherAttrs->font &&\n       textDecoration == otherAttrs->textDecoration &&\n       color == otherAttrs->color &&\n       backgroundColor == otherAttrs->backgroundColor &&\n       backgroundImage == otherAttrs->backgroundImage &&\n       backgroundRepeat == otherAttrs->backgroundRepeat &&\n       backgroundAttachment == otherAttrs->backgroundAttachment &&\n       backgroundPositionX == otherAttrs->backgroundPositionX &&\n       backgroundPositionY == otherAttrs->backgroundPositionY &&\n       textAlign == otherAttrs->textAlign &&\n       valign == otherAttrs->valign &&\n       textAlignChar == otherAttrs->textAlignChar &&\n       textTransform == otherAttrs->textTransform &&\n       vloat == otherAttrs->vloat &&\n       clear == otherAttrs->clear &&\n       overflow == otherAttrs->overflow &&\n       position == otherAttrs->position &&\n       top == otherAttrs->top &&\n       bottom == otherAttrs->bottom &&\n       left == otherAttrs->left &&\n       right == otherAttrs->right &&\n       hBorderSpacing == otherAttrs->hBorderSpacing &&\n       vBorderSpacing == otherAttrs->vBorderSpacing &&\n       wordSpacing == otherAttrs->wordSpacing &&\n       width == otherAttrs->width &&\n       height == otherAttrs->height &&\n       minWidth == otherAttrs->minWidth &&\n       maxWidth == otherAttrs->maxWidth &&\n       minHeight == otherAttrs->minHeight &&\n       maxHeight == otherAttrs->maxHeight &&\n       lineHeight == otherAttrs->lineHeight &&\n       textIndent == otherAttrs->textIndent &&\n       margin.equals (&otherAttrs->margin) &&\n       borderWidth.equals (&otherAttrs->borderWidth) &&\n       padding.equals (&otherAttrs->padding) &&\n       borderCollapse == otherAttrs->borderCollapse &&\n       borderColor.top == otherAttrs->borderColor.top &&\n       borderColor.right == otherAttrs->borderColor.right &&\n       borderColor.bottom == otherAttrs->borderColor.bottom &&\n       borderColor.left == otherAttrs->borderColor.left &&\n       borderStyle.top == otherAttrs->borderStyle.top &&\n       borderStyle.right == otherAttrs->borderStyle.right &&\n       borderStyle.bottom == otherAttrs->borderStyle.bottom &&\n       borderStyle.left == otherAttrs->borderStyle.left &&\n       display == otherAttrs->display &&\n       whiteSpace == otherAttrs->whiteSpace &&\n       listStylePosition == otherAttrs->listStylePosition &&\n       listStyleType == otherAttrs->listStyleType &&\n       cursor == otherAttrs->cursor &&\n       zIndex == otherAttrs->zIndex &&\n       x_link == otherAttrs->x_link &&\n       x_lang[0] == otherAttrs->x_lang[0] &&\n       x_lang[1] == otherAttrs->x_lang[1] &&\n       x_img == otherAttrs->x_img &&\n       x_tooltip == otherAttrs->x_tooltip);\n}\n\nint StyleAttrs::hashValue () {\n   return (intptr_t) font +\n      textDecoration +\n      (intptr_t) color +\n      (intptr_t) backgroundColor +\n      (intptr_t) backgroundImage +\n      backgroundRepeat +\n      backgroundAttachment +\n      backgroundPositionX +\n      backgroundPositionY +\n      textAlign +\n      valign +\n      textAlignChar +\n      textTransform +\n      vloat +\n      clear +\n      overflow +\n      position +\n      top +\n      bottom +\n      left +\n      right +\n      hBorderSpacing +\n      vBorderSpacing +\n      wordSpacing +\n      width +\n      height +\n      minWidth +\n      maxWidth +\n      minHeight +\n      maxHeight +\n      lineHeight +\n      textIndent +\n      margin.hashValue () +\n      borderWidth.hashValue () +\n      padding.hashValue () +\n      borderCollapse +\n      (intptr_t) borderColor.top +\n      (intptr_t) borderColor.right +\n      (intptr_t) borderColor.bottom +\n      (intptr_t) borderColor.left +\n      borderStyle.top +\n      borderStyle.right +\n      borderStyle.bottom +\n      borderStyle.left +\n      display +\n      whiteSpace +\n      listStylePosition +\n      listStyleType +\n      cursor +\n      zIndex +\n      x_link +\n      x_lang[0] + x_lang[1] +\n      x_img +\n      (intptr_t) x_tooltip;\n}\n\nint Style::totalRef = 0;\ncontainer::typed::HashTable <StyleAttrs, Style> * Style::styleTable =\n   new container::typed::HashTable <StyleAttrs, Style> (false, false, 1024);\n\nStyle::Style (StyleAttrs *attrs)\n{\n   DBG_OBJ_CREATE (\"dw::core::style::Style\");\n\n   copyAttrs (attrs);\n\n   DBG_OBJ_ASSOC_CHILD (font);\n   DBG_OBJ_ASSOC_CHILD (color);\n   DBG_OBJ_ASSOC_CHILD (backgroundColor);\n   DBG_OBJ_ASSOC_CHILD (backgroundImage);\n   DBG_OBJ_ASSOC_CHILD (borderColor.top);\n   DBG_OBJ_ASSOC_CHILD (borderColor.bottom);\n   DBG_OBJ_ASSOC_CHILD (borderColor.left);\n   DBG_OBJ_ASSOC_CHILD (borderColor.right);\n   //DBG_OBJ_ASSOC_CHILD (x_tooltip);\n\n   refCount = 1;\n\n   font->ref ();\n   if (color)\n      color->ref ();\n   if (backgroundColor)\n      backgroundColor->ref ();\n   if (backgroundImage)\n      backgroundImage->ref ();\n   if (borderColor.top)\n      borderColor.top->ref();\n   if (borderColor.bottom)\n      borderColor.bottom->ref();\n   if (borderColor.left)\n      borderColor.left->ref();\n   if (borderColor.right)\n      borderColor.right->ref();\n   if (x_tooltip)\n      x_tooltip->ref();\n\n   totalRef++;\n}\n\nStyle::~Style ()\n{\n   font->unref ();\n\n   if (color)\n      color->unref ();\n   if (backgroundColor)\n      backgroundColor->unref ();\n   if (backgroundImage)\n      backgroundImage->unref ();\n   if (borderColor.top)\n      borderColor.top->unref();\n   if (borderColor.bottom)\n      borderColor.bottom->unref();\n   if (borderColor.left)\n      borderColor.left->unref();\n   if (borderColor.right)\n      borderColor.right->unref();\n   if (x_tooltip)\n      x_tooltip->unref();\n\n   styleTable->remove (this);\n   totalRef--;\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid Style::copyAttrs (StyleAttrs *attrs)\n{\n   font = attrs->font;\n   textDecoration = attrs->textDecoration;\n   color = attrs->color;\n   backgroundColor = attrs->backgroundColor;\n   backgroundImage = attrs->backgroundImage;\n   backgroundRepeat = attrs->backgroundRepeat;\n   backgroundAttachment = attrs->backgroundAttachment;\n   backgroundPositionX = attrs->backgroundPositionX;\n   backgroundPositionY = attrs->backgroundPositionY;\n   textAlign = attrs->textAlign;\n   valign = attrs->valign;\n   textAlignChar = attrs->textAlignChar;\n   textTransform = attrs->textTransform;\n   vloat = attrs->vloat;\n   clear = attrs->clear;\n   overflow = attrs->overflow;\n   position = attrs->position;\n   top = attrs->top;\n   bottom = attrs->bottom;\n   left = attrs->left;\n   right = attrs->right;\n   hBorderSpacing = attrs->hBorderSpacing;\n   vBorderSpacing = attrs->vBorderSpacing;\n   wordSpacing = attrs->wordSpacing;\n   width = attrs->width;\n   height = attrs->height;\n   lineHeight = attrs->lineHeight;\n   textIndent = attrs->textIndent;\n   minWidth = attrs->minWidth;\n   maxWidth = attrs->maxWidth;\n   minHeight = attrs->minHeight;\n   maxHeight = attrs->maxHeight;\n   margin = attrs->margin;\n   borderWidth = attrs->borderWidth;\n   padding = attrs->padding;\n   borderCollapse = attrs->borderCollapse;\n   borderColor = attrs->borderColor;\n   borderStyle = attrs->borderStyle;\n   display = attrs->display;\n   whiteSpace = attrs->whiteSpace;\n   listStylePosition = attrs->listStylePosition;\n   listStyleType = attrs->listStyleType;\n   cursor = attrs->cursor;\n   zIndex = attrs->zIndex;\n   x_link = attrs->x_link;\n   x_lang[0] = attrs->x_lang[0];\n   x_lang[1] = attrs->x_lang[1];\n   x_img = attrs->x_img;\n   x_tooltip = attrs->x_tooltip;\n}\n\n// ----------------------------------------------------------------------\n\nbool FontAttrs::equals(object::Object *other)\n{\n   FontAttrs *otherAttrs = (FontAttrs*)other;\n   return\n      this == otherAttrs ||\n      (size == otherAttrs->size &&\n       weight == otherAttrs->weight &&\n       style == otherAttrs->style &&\n       letterSpacing == otherAttrs->letterSpacing &&\n       fontVariant == otherAttrs->fontVariant &&\n       strcmp (name, otherAttrs->name) == 0);\n}\n\nint FontAttrs::hashValue()\n{\n   int h = object::String::hashValue (name);\n   h = (h << 5) - h + size;\n   h = (h << 5) - h + weight;\n   h = (h << 5) - h + style;\n   h = (h << 5) - h + letterSpacing;\n   h = (h << 5) - h + fontVariant;\n   return h;\n}\n\nFont::~Font ()\n{\n   free ((char*)name);\n   DBG_OBJ_DELETE ();\n}\n\nvoid Font::copyAttrs (FontAttrs *attrs)\n{\n   name = strdup (attrs->name);\n   size = attrs->size;\n   weight = attrs->weight;\n   style = attrs->style;\n   letterSpacing = attrs->letterSpacing;\n   fontVariant = attrs->fontVariant;\n}\n\nFont *Font::create0 (Layout *layout, FontAttrs *attrs,\n                     bool tryEverything)\n{\n   return layout->createFont (attrs, tryEverything);\n}\n\nFont *Font::create (Layout *layout, FontAttrs *attrs)\n{\n   return create0 (layout, attrs, false);\n}\n\nbool Font::exists (Layout *layout, const char *name)\n{\n   return layout->fontExists (name);\n}\n\n// ----------------------------------------------------------------------\n\nbool ColorAttrs::equals(object::Object *other)\n{\n   ColorAttrs *oc = (ColorAttrs*)other;\n   return this == oc || (color == oc->color);\n}\n\nint ColorAttrs::hashValue()\n{\n   return color;\n}\n\nColor::~Color ()\n{\n   DBG_OBJ_DELETE ();\n}\n\nint Color::shadeColor (int color, int d)\n{\n   int red = (color >> 16) & 255;\n   int green = (color >> 8) & 255;\n   int blue = color & 255;\n\n   double oldLightness = ((double) misc::max (red, green, blue)) / 255;\n   double newLightness;\n\n   if (oldLightness > 0.8) {\n      if (d > 0)\n         newLightness = oldLightness - 0.2;\n      else\n         newLightness = oldLightness - 0.4;\n   } else if (oldLightness < 0.2) {\n      if (d > 0)\n         newLightness = oldLightness + 0.4;\n      else\n         newLightness = oldLightness + 0.2;\n   } else\n      newLightness = oldLightness + d * 0.2;\n\n   if (oldLightness) {\n      double f = (newLightness / oldLightness);\n      red = (int)(red * f);\n      green = (int)(green * f);\n      blue = (int)(blue * f);\n   } else {\n      red = green = blue = (int)(newLightness * 255);\n   }\n\n   return (red << 16) | (green << 8) | blue;\n}\n\nint Color::shadeColor (int color, Shading shading)\n{\n   switch (shading) {\n   case SHADING_NORMAL:\n      return color;\n\n   case SHADING_LIGHT:\n      return shadeColor(color, +1);\n\n   case SHADING_INVERSE:\n      return color ^ 0xffffff;\n\n  case SHADING_DARK:\n      return shadeColor(color, -1);\n\n   default:\n      // compiler happiness\n      misc::assertNotReached ();\n      return -1;\n   }\n}\n\n\nColor *Color::create (Layout *layout, int col)\n{\n   ColorAttrs attrs(col);\n\n   return layout->createColor (col);\n}\n\nTooltip *Tooltip::create (Layout *layout, const char *text)\n{\n   return layout->createTooltip (text);\n}\n\n// ----------------------------------------------------------------------\n\nvoid StyleImage::StyleImgRenderer::setBuffer (core::Imgbuf *buffer, bool resize)\n{\n   if (image->imgbufSrc)\n      image->imgbufSrc->unref ();\n   if (image->imgbufTiled)\n      image->imgbufTiled->unref ();\n\n   image->imgbufTiled = NULL;\n\n   image->imgbufSrc = buffer;\n   DBG_OBJ_ASSOC (image, image->imgbufSrc);\n\n   if (image->imgbufSrc) {\n      image->imgbufSrc->ref ();\n\n      // If the image is too small, drawing a background will cause\n      // many calls of View::drawImgbuf. For this reason, we create\n      // another image buffer, the \"tiled\" image buffer, which is\n      // larger (the \"optimal\" size is defined as OPT_BG_IMG_W *\n      // OPT_BG_IMG_H) and contains the \"source\" buffer several times.\n      //\n      // This \"tiled\" buffer is not used when 'background-repeat' has\n      // another value than 'repeat', for obvious reasons. Image\n      // buffers only \"tiled\" in one dimension (to optimize 'repeat-x'\n      // and 'repeat-y') are not supported.\n\n      if (image->imgbufSrc->getRootWidth() * image->imgbufSrc->getRootHeight()\n          < MIN_BG_IMG_W * MIN_BG_IMG_H) {\n         image->tilesX =\n            misc::max (OPT_BG_IMG_W / image->imgbufSrc->getRootWidth(), 1);\n         image->tilesY =\n            misc::max (OPT_BG_IMG_H / image->imgbufSrc->getRootHeight(), 1);\n         image->imgbufTiled =\n            image->imgbufSrc->createSimilarBuf\n               (image->tilesX * image->imgbufSrc->getRootWidth(),\n                image->tilesY * image->imgbufSrc->getRootHeight());\n\n         DBG_OBJ_ASSOC (image, image->imgbufTiled);\n      }\n   }\n}\n\nvoid StyleImage::StyleImgRenderer::drawRow (int row)\n{\n   if (image->imgbufTiled) {\n      // A row of data has been copied to the source buffer, here it\n      // is copied into the tiled buffer.\n\n      // Unfortunately, this code may be called *after* some other\n      // implementations of ImgRenderer::drawRow, which actually\n      // *draw* the tiled buffer, which is so not up to date\n      // (ImgRendererDist does not define an order). OTOH, these\n      // drawing implementations calle Widget::queueResize, so the\n      // actual drawing (and so access to the tiled buffer) is done\n      // later.\n\n      int w = image->imgbufSrc->getRootWidth ();\n      int h = image->imgbufSrc->getRootHeight ();\n\n      for (int x = 0; x < image->tilesX; x++)\n         for (int y = 0; y < image->tilesX; y++)\n            image->imgbufSrc->copyTo (image->imgbufTiled, x * w, y * h,\n                                      0, row, w, 1);\n   }\n}\n\nvoid StyleImage::StyleImgRenderer::finish ()\n{\n   // Nothing to do.\n}\n\nvoid StyleImage::StyleImgRenderer::fatal ()\n{\n   // Nothing to do.\n}\n\nStyleImage::StyleImage ()\n{\n   DBG_OBJ_CREATE (\"dw::core::style::StyleImage\");\n\n   refCount = 0;\n   imgbufSrc = NULL;\n   imgbufTiled = NULL;\n\n   imgRendererDist = new ImgRendererDist ();\n   styleImgRenderer = new StyleImgRenderer (this);\n   imgRendererDist->put (styleImgRenderer);\n}\n\nStyleImage::~StyleImage ()\n{\n   if (imgbufSrc)\n      imgbufSrc->unref ();\n   if (imgbufTiled)\n      imgbufTiled->unref ();\n\n   delete imgRendererDist;\n   delete styleImgRenderer;\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid StyleImage::ExternalImgRenderer::setBuffer (core::Imgbuf *buffer,\n                                                 bool resize)\n{\n   // Nothing to do?\n}\n\nvoid StyleImage::ExternalImgRenderer::drawRow (int row)\n{\n   if (drawBackgroundLineByLine) {\n      StyleImage *backgroundImage;\n      if (readyToDraw () && (backgroundImage = getBackgroundImage ())) {\n         // All single rows are drawn.\n\n         Imgbuf *imgbuf = backgroundImage->getImgbufSrc();\n         int imgWidth = imgbuf->getRootWidth ();\n         int imgHeight = imgbuf->getRootHeight ();\n\n         int x, y, width, height;\n         getBgArea (&x, &y, &width, &height);\n\n         int xRef, yRef, widthRef, heightRef;\n         getRefArea (&xRef, &yRef, &widthRef, &heightRef);\n\n         bool repeatX, repeatY, doDraw;\n         int origX, origY, tileX1, tileX2, tileY1, tileY2;\n\n         calcBackgroundRelatedValues (backgroundImage,\n                                      getBackgroundRepeat (),\n                                      getBackgroundAttachment (),\n                                      getBackgroundPositionX (),\n                                      getBackgroundPositionY (),\n                                      x, y, width, height, xRef, yRef, widthRef,\n                                      heightRef, &repeatX, &repeatY, &origX,\n                                      &origY, &tileX1, &tileX2, &tileY1,\n                                      &tileY2, &doDraw);\n\n         //printf (\"tileX1 = %d, tileX2 = %d, tileY1 = %d, tileY2 = %d\\n\",\n         //        tileX1, tileX2, tileY1, tileY2);\n\n         if (doDraw)\n            // Only iterate over y, because the rows can be combined\n            // horizontally.\n            for (int tileY = tileY1; tileY <= tileY2; tileY++) {\n               int x1 = misc::max (origX + tileX1 * imgWidth, x);\n               int x2 = misc::min (origX + (tileX2 + 1) * imgWidth, x + width);\n\n               int yt = origY + tileY * imgHeight + row;\n               if (yt >= y && yt < y + height)\n                  draw (x1, yt, x2 - x1, 1);\n            }\n      }\n   }\n}\n\nvoid StyleImage::ExternalImgRenderer::finish ()\n{\n   if (!drawBackgroundLineByLine) {\n      if (readyToDraw ()) {\n         // Draw total area, as a whole.\n         int x, y, width, height;\n         getBgArea (&x, &y, &width, &height);\n         draw (x, y, width, height);\n      }\n   }\n}\n\nvoid StyleImage::ExternalImgRenderer::fatal ()\n{\n   // Nothing to do.\n}\n\n// ----------------------------------------------------------------------\n\nStyleImage *StyleImage::ExternalWidgetImgRenderer::getBackgroundImage ()\n{\n   Style *style = getStyle ();\n   return style ? style->backgroundImage : NULL;\n}\n\nBackgroundRepeat StyleImage::ExternalWidgetImgRenderer::getBackgroundRepeat ()\n{\n   Style *style = getStyle ();\n   return style ? style->backgroundRepeat : BACKGROUND_REPEAT;\n}\n\nBackgroundAttachment\n   StyleImage::ExternalWidgetImgRenderer::getBackgroundAttachment ()\n{\n   Style *style = getStyle ();\n   return style ? style->backgroundAttachment : BACKGROUND_ATTACHMENT_SCROLL;\n}\n\nLength StyleImage::ExternalWidgetImgRenderer::getBackgroundPositionX ()\n{\n   Style *style = getStyle ();\n   return style ? style->backgroundPositionX : createPerLength (0);\n}\n\nLength StyleImage::ExternalWidgetImgRenderer::getBackgroundPositionY ()\n{\n   Style *style = getStyle ();\n   return style ? style->backgroundPositionY : createPerLength (0);\n}\n\n// ----------------------------------------------------------------------\n\n/*\n * The drawBorder{Top,Bottom,Left,Right} functions are similar. They\n * use a trapezium as draw polygon, or drawTypedLine() for dots and dashes.\n * Although the concept is simple, achieving pixel accuracy is laborious [1].\n *\n * [1] https://dillo-browser.github.io/old/css_compat/tests/border-style.html\n */\nstatic void drawBorderTop(View *view, Style *style,\n                          int x1, int y1, int x2, int y2)\n\n{\n   int d, w;\n   Point points[4];\n   const bool filled = true, convex = true;\n   bool ridge = false, inset = false, dotted = false;\n   Color::Shading shading = Color::SHADING_NORMAL;\n\n   if (!style->borderColor.top || style->borderWidth.top == 0)\n      return;\n\n   switch (style->borderStyle.top) {\n   case BORDER_NONE:\n   case BORDER_HIDDEN:\n      break;\n   case BORDER_DOTTED:\n      dotted = true;\n   case BORDER_DASHED:\n      w = style->borderWidth.top;\n      view->drawTypedLine(style->borderColor.top, shading,\n                          dotted ? LINE_DOTTED : LINE_DASHED,\n                          w, x1+w/2, y1+w/2, x2-w/2, y2+w/2);\n      break;\n   case BORDER_SOLID:\n   case BORDER_INSET:\n      inset = true;\n   case BORDER_OUTSET:\n      if (style->borderStyle.top != BORDER_SOLID)\n         shading = (inset) ? Color::SHADING_DARK : Color::SHADING_LIGHT;\n\n      if (style->borderWidth.top == 1) {\n         view->drawLine(style->borderColor.top, shading, x1, y1, x2, y2);\n      } else {\n         points[0].x = x1;\n         points[1].x = x2 + 1;\n         points[0].y = points[1].y = y1;\n         points[2].x = points[1].x - style->borderWidth.right;\n         points[3].x = x1 + style->borderWidth.left;\n         points[2].y = points[3].y = points[0].y + style->borderWidth.top;\n         view->drawPolygon (style->borderColor.top, shading, filled, convex,\n                            points, 4);\n      }\n      break;\n   case BORDER_RIDGE:\n      ridge = true;\n   case BORDER_GROOVE:\n      d = style->borderWidth.top & 1;\n      points[0].x = x1;\n      points[1].x = x2 + 1;\n      points[0].y = points[1].y = y1;\n      points[2].x = x2 - style->borderWidth.right / 2;\n      points[3].x = x1 + style->borderWidth.left / 2;\n      points[2].y = points[3].y = y1 + style->borderWidth.top / 2 + d;\n      shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK;\n      view->drawPolygon (style->borderColor.top, shading, filled, convex,\n                         points, 4);\n      points[0].x = x1 + style->borderWidth.left / 2 + d;\n      points[1].x = x2 - style->borderWidth.right / 2 + 1 - d;\n      points[0].y = points[1].y = y1 + style->borderWidth.top / 2 + d;\n      points[2].x = x2 - style->borderWidth.right + 1 - d;\n      points[3].x = x1 + style->borderWidth.left;\n      points[2].y = points[3].y = y1 + style->borderWidth.top;\n      shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT;\n      view->drawPolygon (style->borderColor.top, shading, filled, convex,\n                         points, 4);\n      break;\n   case BORDER_DOUBLE:\n      w = (int) rint(style->borderWidth.top / 3.0);\n      d = w ? style->borderWidth.top - 2 * w : 0;\n      int w_l = (int) rint(style->borderWidth.left / 3.0);\n      int w_r = (int) rint(style->borderWidth.right / 3.0);\n      if (style->borderWidth.top == 1) {\n         view->drawLine(style->borderColor.top, shading, x1, y1, x2, y2);\n         break;\n      }\n      points[0].x = x1;\n      points[1].x = x2 + 1;\n      points[0].y = points[1].y = y1;\n      points[2].x = points[1].x - w_r;\n      points[3].x = points[0].x + w_l;\n      points[2].y = points[3].y = points[0].y + w;\n      view->drawPolygon (style->borderColor.top, shading, filled, convex,\n                         points, 4);\n      points[0].x = x1 + style->borderWidth.left - w_l;\n      points[1].x = x2 + 1 - style->borderWidth.right + w_r;\n      points[0].y = points[1].y = y1 + w + d;\n      points[2].x = x2 + 1 - style->borderWidth.right;\n      points[3].x = x1 + style->borderWidth.left;\n      points[2].y = points[3].y = y1 + style->borderWidth.top;\n      view->drawPolygon (style->borderColor.top, shading, filled, convex,\n                         points, 4);\n      break;\n   }\n}\n\nstatic void drawBorderBottom(View *view, Style *style,\n                             int x1, int y1, int x2, int y2)\n\n{\n   int d, w;\n   Point points[4];\n   const bool filled = true, convex = true;\n   bool ridge = false, inset = false, dotted = false;\n   Color::Shading shading = Color::SHADING_NORMAL;\n\n   if (!style->borderColor.bottom || style->borderWidth.bottom == 0)\n      return;\n\n   switch (style->borderStyle.bottom) {\n   case BORDER_NONE:\n   case BORDER_HIDDEN:\n      break;\n   case BORDER_DOTTED:\n      dotted = true;\n   case BORDER_DASHED:\n      w = style->borderWidth.bottom;\n      view->drawTypedLine(style->borderColor.bottom, shading,\n                          dotted ? LINE_DOTTED : LINE_DASHED,\n                          w, x1+w/2, y1-w/2, x2-w/2, y2-w/2);\n      break;\n   case BORDER_SOLID:\n   case BORDER_INSET:\n      inset = true;\n   case BORDER_OUTSET:\n      if (style->borderStyle.bottom != BORDER_SOLID)\n         shading = (inset) ? Color::SHADING_LIGHT : Color::SHADING_DARK;\n\n      if (style->borderWidth.bottom == 1) { /* 1 pixel line */\n         view->drawLine(style->borderColor.bottom, shading, x1, y1, x2, y2);\n      } else {\n         points[0].x = x1 - 1;\n         points[1].x = x2 + 2;\n         points[0].y = points[1].y = y1 + 1;\n         points[2].x = points[1].x - style->borderWidth.right;\n         points[3].x = points[0].x + style->borderWidth.left;\n         points[2].y = points[3].y = points[0].y-style->borderWidth.bottom;\n         view->drawPolygon (style->borderColor.bottom, shading, filled, convex,\n                            points, 4);\n      }\n      break;\n   case BORDER_RIDGE:\n      ridge = true;\n   case BORDER_GROOVE:\n      w = style->borderWidth.bottom;\n      d = w & 1;\n      points[0].x = x1 - 1;\n      points[1].x = x2 + 2 - d;\n      points[0].y = points[1].y = y1 + 1;\n      points[2].x = points[1].x - style->borderWidth.right / 2;\n      points[3].x = points[0].x + style->borderWidth.left / 2 + d;\n      points[2].y = points[3].y = points[0].y - w/2 - d;\n      shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT;\n      view->drawPolygon (style->borderColor.bottom, shading, filled, convex,\n                         points, 4);\n      // clockwise\n      points[0].x = x1 + style->borderWidth.left - 1;\n      points[1].x = x2 + 1 - style->borderWidth.right + 1;\n      points[0].y = points[1].y = y1 - w + 1;\n      points[2].x = points[1].x + style->borderWidth.right / 2;\n      points[3].x = points[0].x - style->borderWidth.left / 2;\n      points[2].y = points[3].y = points[0].y + w/2;\n      shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK;\n      view->drawPolygon (style->borderColor.bottom, shading, filled, convex,\n                         points, 4);\n      break;\n   case BORDER_DOUBLE:\n      w = (int) rint(style->borderWidth.bottom / 3.0);\n      d = w ? style->borderWidth.bottom - 2 * w : 0;\n      int w_l = (int) rint(style->borderWidth.left / 3.0);\n      int w_r = (int) rint(style->borderWidth.right / 3.0);\n      if (style->borderWidth.bottom == 1) {\n         view->drawLine(style->borderColor.bottom, shading, x1, y1, x2, y2);\n         break;\n      }\n      points[0].x = x2 + 2;\n      points[1].x = x1 - 1;\n      points[0].y = points[1].y = y1 + 1;\n      points[2].x = points[1].x + w_l;\n      points[3].x = points[0].x - w_r;\n      points[2].y = points[3].y = points[0].y - w;\n      view->drawPolygon (style->borderColor.bottom, shading, filled, convex,\n                         points, 4);\n      points[0].x = x2 + 2 - style->borderWidth.right + w_r;\n      points[1].x = x1 - 1 + style->borderWidth.left - w_l;\n      points[0].y = points[1].y = y1 + 1 - w - d;\n      points[2].x = x1 - 1 + style->borderWidth.left;\n      points[3].x = x2 + 2 - style->borderWidth.right;\n      points[2].y = points[3].y = y1 + 1 - style->borderWidth.bottom;\n      view->drawPolygon (style->borderColor.bottom, shading, filled, convex,\n                         points, 4);\n      break;\n   }\n}\n\nstatic void drawBorderLeft(View *view, Style *style,\n                           int x1, int y1, int x2, int y2)\n\n{\n   int d, w;\n   Point points[4];\n   bool filled = true, convex = true;\n   bool ridge = false, inset = false, dotted = false;\n   Color::Shading shading = Color::SHADING_NORMAL;\n\n   if (!style->borderColor.left || style->borderWidth.left == 0)\n      return;\n\n   switch (style->borderStyle.left) {\n   case BORDER_NONE:\n   case BORDER_HIDDEN:\n      break;\n   case BORDER_DOTTED:\n      dotted = true;\n   case BORDER_DASHED:\n      w = style->borderWidth.left;\n      view->drawTypedLine(style->borderColor.left, shading,\n                          dotted ? LINE_DOTTED : LINE_DASHED,\n                          w, x1+w/2, y1+w/2, x1+w/2, y2-w/2);\n      break;\n   case BORDER_SOLID:\n   case BORDER_INSET:\n      inset = true;\n   case BORDER_OUTSET:\n      if (style->borderStyle.left != BORDER_SOLID)\n         shading = (inset) ? Color::SHADING_DARK : Color::SHADING_LIGHT;\n      if (style->borderWidth.left == 1) { /* 1 pixel line */\n         view->drawLine(style->borderColor.left, shading, x1, y1, x2, y2);\n      } else {\n         points[0].x = points[1].x = x1;\n         points[0].y = y1 - 1;\n         points[1].y = y2 + 1;\n         points[2].x = points[3].x = points[0].x + style->borderWidth.left;\n         points[2].y = points[1].y - style->borderWidth.bottom;\n         points[3].y = points[0].y + style->borderWidth.top;\n         view->drawPolygon (style->borderColor.left, shading, filled, convex,\n                            points, 4);\n      }\n      break;\n   case BORDER_RIDGE:\n      ridge = true;\n   case BORDER_GROOVE:\n      w = style->borderWidth.left;\n      d = w & 1;\n      points[0].x = points[1].x = x1;\n      points[0].y = y1;\n      points[1].y = y2;\n      points[2].x = points[3].x = x1 + w / 2 + d;\n      points[2].y = y2 - style->borderWidth.bottom / 2;\n      points[3].y = y1 + style->borderWidth.top / 2;\n      shading = (ridge) ? Color::SHADING_LIGHT : Color::SHADING_DARK;\n      view->drawPolygon (style->borderColor.left, shading, filled, convex,\n                         points, 4);\n      points[0].x = points[1].x = x1 + w / 2 + d;\n      points[0].y = y1 + style->borderWidth.top / 2;\n      points[1].y = y2 - style->borderWidth.bottom / 2;\n      points[2].x = points[3].x = x1 + w;\n      points[2].y = y2 - style->borderWidth.bottom;\n      points[3].y = y1 + style->borderWidth.top;\n      shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT;\n      view->drawPolygon (style->borderColor.left, shading, filled, convex,\n                         points, 4);\n      break;\n   case BORDER_DOUBLE:\n      w = (int) rint(style->borderWidth.left / 3.0);\n      d = w ? style->borderWidth.left - 2 * w : 0;\n      int w_b = (int) rint(style->borderWidth.bottom / 3.0);\n      int w_t = (int) rint(style->borderWidth.top / 3.0);\n      if (style->borderWidth.left == 1) {\n         view->drawLine(style->borderColor.left, shading, x1, y1, x2, y2-1);\n         break;\n      }\n      points[0].x = points[1].x = x1;\n      points[0].y = y1 - 1;\n      points[1].y = y2 + 1;\n      points[2].x = points[3].x = points[0].x + w;\n      points[2].y = points[1].y - w_b;\n      points[3].y = points[0].y + w_t;\n      view->drawPolygon (style->borderColor.left, shading, filled, convex,\n                         points, 4);\n      points[0].x = points[1].x = x1 + w + d;\n      points[0].y = y1 - 1 + style->borderWidth.top - w_t;\n      points[1].y = y2 + 1 - style->borderWidth.bottom + w_b;\n      points[2].x = points[3].x = points[0].x + w;\n      points[2].y = y2 + 1 - style->borderWidth.bottom;\n      points[3].y = y1 - 1 + style->borderWidth.top;\n      view->drawPolygon (style->borderColor.left, shading, filled, convex,\n                         points, 4);\n      break;\n   }\n}\n\nstatic void drawBorderRight(View *view, Style *style,\n                            int x1, int y1, int x2, int y2)\n\n{\n   int d, w;\n   Point points[4];\n   const bool filled = true, convex = true;\n   bool ridge = false, inset = false, dotted = false;\n   Color::Shading shading = Color::SHADING_NORMAL;\n\n   if (!style->borderColor.right || style->borderWidth.right == 0)\n      return;\n\n   switch (style->borderStyle.right) {\n   case BORDER_NONE:\n   case BORDER_HIDDEN:\n      break;\n   case BORDER_DOTTED:\n      dotted = true;\n   case BORDER_DASHED:\n      w = style->borderWidth.right;\n      view->drawTypedLine(style->borderColor.right, shading,\n                          dotted ? LINE_DOTTED : LINE_DASHED,\n                          w, x1 - w/2, y1 + w/2, x1 - w/2, y2 - w/2);\n      break;\n   case BORDER_SOLID:\n   case BORDER_INSET:\n      inset = true;\n   case BORDER_OUTSET:\n      if (style->borderStyle.right != BORDER_SOLID)\n         shading = (inset) ? Color::SHADING_LIGHT : Color::SHADING_DARK;\n      if (style->borderWidth.right == 1) { /* 1 pixel line */\n         view->drawLine(style->borderColor.right, shading, x1, y1, x2, y2);\n      } else {\n         points[0].x = points[1].x = x1 + 1;\n         points[0].y = y1 - 1;\n         points[1].y = y2 + 1;\n         points[2].x = points[3].x = points[0].x-style->borderWidth.right;\n         points[2].y = points[1].y - style->borderWidth.bottom;\n         points[3].y = points[0].y + style->borderWidth.top;\n         view->drawPolygon (style->borderColor.right, shading, filled, convex,\n                            points,4);\n      }\n      break;\n   case BORDER_RIDGE:\n      ridge = true;\n   case BORDER_GROOVE:\n      w = style->borderWidth.right;\n      d = w & 1;\n      points[0].x = points[1].x = x1 + 1;\n      points[0].y = y1;\n      points[1].y = y2;\n      points[2].x = points[3].x = points[0].x - w / 2 - d;\n      points[2].y = y2 - style->borderWidth.bottom / 2;\n      points[3].y = points[0].y + style->borderWidth.top / 2;\n      shading = (ridge) ? Color::SHADING_DARK : Color::SHADING_LIGHT;\n      view->drawPolygon (style->borderColor.right, shading, filled, convex,\n                         points, 4);\n      points[0].x = points[1].x = x1 + 1 - w / 2 - d;\n      points[0].y = y1 + style->borderWidth.top / 2;\n      points[1].y = y2 - style->borderWidth.bottom / 2;\n      points[2].x = points[3].x = x1 + 1 - w;\n      points[2].y = y2 - style->borderWidth.bottom;\n      points[3].y = y1 + style->borderWidth.top;\n      shading = (ridge) ? Color::SHADING_LIGHT: Color::SHADING_DARK;\n      view->drawPolygon (style->borderColor.right, shading, filled, convex,\n                         points, 4);\n      break;\n   case BORDER_DOUBLE:\n      w = (int) rint(style->borderWidth.right / 3.0);\n      d = w ? style->borderWidth.right - 2 * w : 0;\n      int w_b = (int) rint(style->borderWidth.bottom / 3.0);\n      int w_t = (int) rint(style->borderWidth.top / 3.0);\n      if (style->borderWidth.right == 1) {\n         view->drawLine(style->borderColor.right, shading, x1, y1, x2, y2);\n         break;\n      }\n      points[0].x = points[1].x = x1 + 1;\n      points[0].y = y1 - 1;\n      points[1].y = y2 + 1;\n      points[2].x = points[3].x = points[0].x - w;\n      points[2].y = points[1].y - w_b;\n      points[3].y = points[0].y + w_t;\n      view->drawPolygon (style->borderColor.right, shading, filled, convex,\n                         points, 4);\n      points[0].x = points[1].x = x1 + 1 - w - d;\n      points[0].y = y1 - 1 + style->borderWidth.top - w_t;\n      points[1].y = y2 + 1 - style->borderWidth.bottom + w_b;\n      points[2].x = points[3].x = points[0].x - w;\n      points[2].y = y2 + 1 - style->borderWidth.bottom;\n      points[3].y = y1 - 1 + style->borderWidth.top;\n      view->drawPolygon (style->borderColor.right, shading, filled, convex,\n                         points, 4);\n      break;\n   }\n}\n\n/**\n * \\brief Draw the border of a region in window, according to style.\n *\n * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox.\n *\n * \"area\" is the area to be drawn, \"x\", \"y\", \"width\" and \"height\"\n * define the box itself. All are given in canvas coordinates.\n */\nvoid drawBorder (View *view, Layout *layout, Rectangle *area,\n                 int x, int y, int width, int height,\n                 Style *style, bool inverse)\n{\n   /** \\todo a lot! */\n   int xb1, yb1, xb2, yb2;\n\n   // top left and bottom right point of outer border boundary\n   xb1 = x + style->margin.left;\n   yb1 = y + style->margin.top;\n   xb2 = x + (width > 0 ? width - 1 : 0) - style->margin.right;\n   yb2 = y + (height > 0 ? height - 1 : 0) - style->margin.bottom;\n\n   /*\n      // top left and bottom right point of inner border boundary\n      xp1 = xb1 + style->borderWidth.left;\n      yp1 = yb1 + style->borderWidth.top;\n      xp2 = xb2 - style->borderWidth.right;\n      yp2 = yb2 - style->borderWidth.bottom;\n\n      light = inverse ? Color::SHADING_DARK : Color::SHADING_LIGHT;\n      dark = inverse ? Color::SHADING_LIGHT : Color::SHADING_DARK;\n      normal = inverse ? Color::SHADING_INVERSE : Color::SHADING_NORMAL;\n   */\n\n   drawBorderRight(view, style, xb2, yb1, xb2, yb2);\n   drawBorderLeft(view, style, xb1, yb1, xb1, yb2);\n   drawBorderTop(view, style, xb1, yb1, xb2, yb1);\n   drawBorderBottom(view, style, xb1, yb2, xb2, yb2);\n}\n\n\n/**\n * \\brief Draw the background (content plus padding) of a region in window,\n *    according to style.\n *\n * Used by dw::core::Widget::drawBox and dw::core::Widget::drawWidgetBox.\n *\n * \"area\" is the area to be drawn, \"x\", \"y\", \"width\" and \"height\"\n * define the box itself (padding box). \"xRef\", \"yRef\", \"widthRef\" and\n * \"heightRef\" define the reference area, which is important for the\n * tiling of background images (for position 0%/0%, a tile is set at\n * xRef/yRef; for position 100%/100%, a tile is set at xRef +\n * widthRef/yRef + widthRef). See calls for more informations; in most\n * cases, these boxes are identical (padding box). All these\n * coordinates are given in canvas coordinates.\n *\n * \"atTop\" should be true, only if the area is drawn directly on the\n * canvas, not on top of other areas; this is only true for the\n * toplevel widget itself (not parts of its contents). Toplevel widget\n * background colors are already set as viewport background color, so\n * that drawing again is is not neccessary, but some time can be\n * saved.\n *\n * Otherwise, the caller should not try to increase the performance by\n * doing some tests before; this is all done in this method.\n * \n * \"bgColor\" is passes implicitly. For non-inversed drawing,\n * style->backgroundColor may simply used. However, when drawing is\n * inversed, and style->backgroundColor is undefined (NULL), a\n * background color defined higher in the hierarchy (which is not\n * accessable here) must be used.\n *\n * (Background *images* are never drawn inverse.)\n */\nvoid drawBackground (View *view, Layout *layout, Rectangle *area,\n                     int x, int y, int width, int height,\n                     int xRef, int yRef, int widthRef, int heightRef,\n                     Style *style, Color *bgColor, bool inverse, bool atTop)\n{\n   bool hasBgColor = bgColor != NULL &&\n      // The test for background colors is rather simple, since only the color\n      // has to be compared, ...\n      (!atTop || layout->getBgColor () != bgColor);\n   bool hasBgImage = (style->backgroundImage != NULL &&\n                      style->backgroundImage->getImgbufSrc() != NULL) &&\n      // ... but for backgrounds, it would be rather complicated. To handle the\n      // two cases (normal HTML in a viewport, where the layout background\n      // image is set, and contents of <button> within a flat view, where the\n      // background image of the toplevel widget is set), only the background\n      // images are compared. A full test, which also deals with all other\n      // attributes related to background images (repeat, position etc.) would\n      // be complicated and useless, so not worth the work.\n      (!atTop || layout->getBgImage () != style->backgroundImage);\n\n   // Since widgets are always drawn from top to bottom, it is *not*\n   // necessary to draw the background if background color and image\n   // are not set (NULL), i. e. shining through.\n\n   if (hasBgColor || hasBgImage) {\n      Rectangle bgArea, intersection;\n      bgArea.x = x;\n      bgArea.y = y;\n      bgArea.width = width;\n      bgArea.height = height;\n\n      if (area->intersectsWith (&bgArea, &intersection)) {\n         if (hasBgColor)\n            view->drawRectangle (bgColor,\n                                 inverse ?\n                                 Color::SHADING_INVERSE : Color::SHADING_NORMAL,\n                                 true, intersection.x, intersection.y,\n                                 intersection.width, intersection.height);\n\n         if (hasBgImage)\n            drawBackgroundImage (view, style->backgroundImage,\n                                 style->backgroundRepeat,\n                                 style->backgroundAttachment,\n                                 style->backgroundPositionX,\n                                 style->backgroundPositionY,\n                                 intersection.x, intersection.y,\n                                 intersection.width, intersection.height,\n                                 xRef, yRef, widthRef, heightRef);\n\n      }\n   }\n}\n\nvoid drawBackgroundImage (View *view, StyleImage *backgroundImage,\n                          BackgroundRepeat backgroundRepeat,\n                          BackgroundAttachment backgroundAttachment,\n                          Length backgroundPositionX,\n                          Length backgroundPositionY,\n                          int x, int y, int width, int height,\n                          int xRef, int yRef, int widthRef, int heightRef)\n{\n   //printf (\"drawBackgroundImage (..., [img: %d, %d], ..., (%d, %d), %d x %d, \"\n   //        \"(%d, %d), %d x %d)\\n\", imgWidth, imgHeight, x, y, width, height,\n   //        xRef, yRef, widthRef, heightRef);\n\n   bool repeatX, repeatY, doDraw;\n   int origX, origY, tileX1, tileX2, tileY1, tileY2;\n\n   calcBackgroundRelatedValues (backgroundImage, backgroundRepeat,\n                                backgroundAttachment, backgroundPositionX,\n                                backgroundPositionY, x, y, width, height,\n                                xRef, yRef, widthRef, heightRef,\n                                &repeatX, &repeatY, &origX, &origY,\n                                &tileX1, &tileX2, &tileY1, &tileY2, &doDraw);\n\n   //printf (\"tileX1 = %d, tileX2 = %d, tileY1 = %d, tileY2 = %d\\n\",\n   //        tileX1, tileX2, tileY1, tileY2);\n\n   if (doDraw) {\n      // Drawing is done with the \"tiled\" buffer, but all calculations\n      // before have been done with the \"source\" buffer.\n\n      Imgbuf *imgbufS = backgroundImage->getImgbufSrc();\n      int imgWidthS = imgbufS->getRootWidth ();\n      int imgHeightS = imgbufS->getRootHeight ();\n\n      Imgbuf *imgbufT = backgroundImage->getImgbufTiled(repeatX, repeatY);\n      int imgWidthT = imgbufT->getRootWidth ();\n      int imgHeightT = imgbufT->getRootHeight ();\n      int tilesX = backgroundImage->getTilesX (repeatX, repeatY);\n      int tilesY = backgroundImage->getTilesY (repeatX, repeatY);\n\n      for (int tileX = tileX1; tileX <= tileX2; tileX += tilesX)\n         for (int tileY = tileY1; tileY <= tileY2; tileY += tilesY) {\n            int xt = origX + tileX * imgWidthS;\n            int x1 = misc::max (xt, x);\n            int x2 = misc::min (xt + imgWidthT, x + width);\n            int yt = origY + tileY * imgHeightS;\n            int y1 = misc::max (yt, y);\n            int y2 = misc::min (yt + imgHeightT, y + height);\n\n            view->drawImage (imgbufT, xt, yt, x1 - xt, y1 - yt,\n                             x2 - x1, y2 - y1);\n         }\n   }\n}\n\nvoid calcBackgroundRelatedValues (StyleImage *backgroundImage,\n                                  BackgroundRepeat backgroundRepeat,\n                                  BackgroundAttachment backgroundAttachment,\n                                  Length backgroundPositionX,\n                                  Length backgroundPositionY,\n                                  int xDraw, int yDraw, int widthDraw,\n                                  int heightDraw, int xRef, int yRef,\n                                  int widthRef, int heightRef, bool *repeatX,\n                                  bool *repeatY, int *origX, int *origY,\n                                  int *tileX1, int *tileX2, int *tileY1,\n                                  int *tileY2, bool *doDraw)\n{\n   Imgbuf *imgbuf = backgroundImage->getImgbufSrc();\n   int imgWidth = imgbuf->getRootWidth ();\n   int imgHeight = imgbuf->getRootHeight ();\n\n   *repeatX = backgroundRepeat == BACKGROUND_REPEAT ||\n      backgroundRepeat == BACKGROUND_REPEAT_X;\n   *repeatY = backgroundRepeat == BACKGROUND_REPEAT ||\n      backgroundRepeat == BACKGROUND_REPEAT_Y;\n\n   *origX = xRef +\n      (isPerLength (backgroundPositionX) ?\n       multiplyWithPerLength (widthRef - imgWidth, backgroundPositionX) :\n       absLengthVal (backgroundPositionX));\n   *origY = yRef +\n      (isPerLength (backgroundPositionY) ?\n       multiplyWithPerLength (heightRef - imgHeight, backgroundPositionY) :\n       absLengthVal (backgroundPositionY));\n\n   *tileX1 = xDraw < *origX ?\n      - (*origX - xDraw + imgWidth - 1) / imgWidth :\n      (xDraw - *origX) / imgWidth;\n   *tileX2 = *origX < xDraw + widthDraw ?\n      (xDraw + widthDraw - *origX - 1) / imgWidth :\n      - (*origX - (xDraw + widthDraw) + imgWidth - 1) / imgWidth;\n   *tileY1 = yDraw < *origY ?\n      - (*origY - yDraw + imgHeight - 1) / imgHeight :\n      (yDraw - *origY) / imgHeight;\n   *tileY2 = *origY < yDraw + heightDraw ?\n      (yDraw + heightDraw - *origY - 1) / imgHeight :\n      - (*origY - (yDraw + heightDraw) + imgHeight - 1) / imgHeight;\n\n   *doDraw = true;\n   if (!*repeatX) {\n      // Only center tile (tileX = 0) is drawn, ...\n      if (*tileX1 <= 0 && *tileX2 >= 0)\n         // ... and is visible.\n         *tileX1 = *tileX2 = 0;\n      else\n         // ... but is not visible.\n         *doDraw = false;\n   }\n\n   if (!*repeatY) {\n      // Analogue.\n      if (*tileY1 <= 0 && *tileY2 >= 0)\n         *tileY1 = *tileY2 = 0;\n      else\n         *doDraw = false;\n   }\n}\n\n// ----------------------------------------------------------------------\n\nstatic const char\n   *const roman_I0[] = { \"\",\"I\",\"II\",\"III\",\"IV\",\"V\",\"VI\",\"VII\",\"VIII\",\"IX\" },\n   *const roman_I1[] = { \"\",\"X\",\"XX\",\"XXX\",\"XL\",\"L\",\"LX\",\"LXX\",\"LXXX\",\"XC\" },\n   *const roman_I2[] = { \"\",\"C\",\"CC\",\"CCC\",\"CD\",\"D\",\"DC\",\"DCC\",\"DCCC\",\"CM\" },\n   *const roman_I3[] = { \"\",\"M\",\"MM\",\"MMM\",\"MMMM\" };\n\nstatic void strAsciiTolower (char *s)\n{\n   for ( ; *s; s++)\n      *s = misc::AsciiTolower (*s);\n}\n\n/**\n * \\brief Convert a number into a string, in a given list style.\n *\n * Used for ordered lists.\n */\nvoid numtostr (int num, char *buf, int buflen, ListStyleType listStyleType)\n{\n   int i3, i2, i1, i0;\n   bool low = false;\n   int start_ch = 'A';\n\n   if (buflen <= 0)\n      return;\n\n   switch(listStyleType){\n   case LIST_STYLE_TYPE_LOWER_ALPHA:\n   case LIST_STYLE_TYPE_LOWER_LATIN:\n      start_ch = 'a';\n   case LIST_STYLE_TYPE_UPPER_ALPHA:\n   case LIST_STYLE_TYPE_UPPER_LATIN:\n      i0 = num - 1;\n      i1 = i0/26 - 1; i2 = i1/26 - 1;\n      if (i2 > 25) /* more than 26+26^2+26^3=18278 elements ? */\n         snprintf(buf, buflen, \"****.\");\n      else\n         snprintf(buf, buflen, \"%c%c%c.\",\n                 i2<0 ? ' ' : start_ch + i2%26,\n                 i1<0 ? ' ' : start_ch + i1%26,\n                 i0<0 ? ' ' : start_ch + i0%26);\n      break;\n   case LIST_STYLE_TYPE_LOWER_ROMAN:\n      low = true;\n   case LIST_STYLE_TYPE_UPPER_ROMAN:\n      i0 = num;\n      i1 = i0/10; i2 = i1/10; i3 = i2/10;\n      i0 %= 10;   i1 %= 10;   i2 %= 10;\n      if (num < 0 || i3 > 4) /* more than 4999 elements ? */\n         snprintf(buf, buflen, \"****.\");\n      else\n         snprintf(buf, buflen, \"%s%s%s%s.\", roman_I3[i3], roman_I2[i2],\n                  roman_I1[i1], roman_I0[i0]);\n      break;\n   case LIST_STYLE_TYPE_DECIMAL:\n   default:\n      snprintf(buf, buflen, \"%d.\", num);\n      break;\n   }\n\n   // ensure termination\n   buf[buflen - 1] = '\\0';\n\n   if (low)\n      strAsciiTolower(buf);\n\n}\n\n} // namespace style\n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/style.hh",
    "content": "#ifndef __DW_STYLE_HH__\n#define __DW_STYLE_HH__\n\n#include <stdint.h>\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\n#include <limits.h>\n\n#include \"../lout/signal.hh\"\n#include \"../lout/debug.hh\"\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief Anything related to Dillo %Widget styles is defined here.\n *\n * <h3>Overview</h3>\n *\n * dw::core::style::Style provides some resources and attributes for\n * drawing widgets, as well as for parts of a widget (e.g., dw::Textblock\n * uses styles for its words). Creating a style is done by filling a\n * dw::core::style::StyleAttrs with the attributes and calling\n * dw::core::style::Style::create:\n *\n * \\code\n * dw::core::style::Style styleAttrs;\n * dw::core::style::Style *style;\n * dw::core::Layout *layout;\n *\n * // ...\n *\n * styleAttrs.foo = bar;\n * // etc.\n * style = dw::core::style::Style::create (&styleAttrs, layout);\n * // do something with style\n * \\endcode\n *\n * After this, the attributes of a dw::core::style::Style should not be\n * changed anymore, since styles are often shared between different\n * widgets etc. (see below). Most times, you simply copy the attributes\n * of another style (possible, since dw::core::style::Style is a sub\n * class of dw::core::style::StyleAttrs), modify them and create a new\n * style:\n *\n * \\code\n * styleAttrs = *anotherStyle;\n * styleAttrs.foo = baz;\n * style = dw::core::style::Style::create (&styleAttrs, layout);\n * \\endcode\n *\n * The dw::core::style::Font structure can be created by\n * dw::core::style::Font::create, in a similar, with\n * dw::core::style::FontAttrs, and colors by\n * dw::core::style::Color::create, passing 0xrrggbb as an\n * argument. Furthermore, there is dw::core::style::Tooltip, created by\n * dw::core::style::Tooltip::create.\n *\n * Notice that fonts, colors and tooltips are only intended to be used in\n * conjunction with dw::core::style::Style.\n *\n *\n * <h3>Naming</h3>\n *\n * dw::core::style::Style will become important for CSS, each CSS\n * attribute, which is supported by dillo, will refer to an attribute in\n * dw::core::style::Style. For this reason, the attributes in\n * dw::core::style::Style get the names from the CSS attributes, with\n * \"camelCase\" instead of hyphens (e.g. \"background-color\" becomes\n * \"backgroundColor\").\n *\n * However, dw::core::style::Style will be extended by some more\n * attributes, which are not defined by CSS. To distinguish them, they\n * get the prefix \"x_\", e.g. dw::core::style::Style::x_link.\n *\n *\n * <h3>Lengths and Percentages</h3>\n *\n * dw::core::style::Length is a simple data type for lengths and\n * percentages:\n *\n * <ul>\n * <li> A length refers to an absolute measurement. It is used to\n *      represent the HTML type %Pixels; and the CSS type \\<length\\>.\n *\n *      For CSS lengths, there are two units: (i) pixels and absolute\n *      units, which have to be converted to pixels (a pixel is, unlike\n *      in the CSS specification, treated as absolute unit), and (ii) the\n *      relative units \"em\" and \"ex\" (see below).\n *\n * <li> A percentage refers to a value relative to another value. It is\n *      used for the HTML type %Length; (except %Pixels;), and the CSS\n *      type \\<percentage\\>.\n *\n * <li> A relative length can be used in lists of HTML MultiLengths.\n * </ul>\n *\n * Since many values in CSS may be either lengths or percentages, a\n * single type is very useful.\n *\n * <h4>Useful Functions</h4>\n *\n * Creating lengths:\n *\n * <ul>\n * <li> dw::core::style::createAbsLength\n * <li> dw::core::style::createPerLength\n * <li> dw::core::style::createRelLength\n * </ul>\n *\n * Examine lengths:\n *\n * <ul>\n * <li> dw::core::style::isAbsLength\n * <li> dw::core::style::isPerLength\n * <li> dw::core::style::isRelLength\n * <li> dw::core::style::absLengthVal\n * <li> dw::core::style::perLengthVal\n * <li> dw::core::style::relLengthVal\n * </ul>\n *\n *\n * <h3>Boxes</h3>\n *\n * <h4>The CSS %Box Model</h4>\n *\n * For borders, margins etc., the box model defined by CSS2 is\n * used. dw::core::style::Style contains some members defining these\n * attributes. A dw::core::Widget must use these values for any\n * calculation of sizes. There are some helper functions (see\n * dw/style.hh). A dw::core::style::Style box looks quite similar to a\n * CSS box:\n *\n * \\image html dw-style-box-model.png\n *\n * <h4>Background colors</h4>\n *\n * The background color is stored in\n * dw::core::style::Style::backgroundColor, which may be NULL (the\n * background color of the parent widget is shining through).\n *\n * For toplevel widgets, this color is set as the background color of the\n * views (dw::core::View::setBgColor), for other widgets, a filled\n * rectangle is drawn, covering the content and padding. (This is\n * compliant with CSS2, the background color of the toplevel element\n * covers the whole canvas.)\n *\n * <h4>Drawing</h4>\n *\n * The following methods may be useful:\n *\n * <ul>\n * <li> dw::core::Widget::drawWidgetBox for drawing the box of a widget\n *      (typically at the beginning of the implementation of\n *      dw::core::Widget::draw), and\n *\n * <li> dw::core::Widget::drawBox, for drawing parts of a widget (e.g.\n *      dw::Textblock::Word, which has its own dw::Textblock::Word::style).\n * </ul>\n *\n *\n * <h3>Notes on Memory Management</h3>\n *\n * Memory management is done by reference counting,\n * dw::core::style::Style::create returns a pointer to\n * dw::core::style::Style with an increased reference counter, so you\n * should care about calling dw::core::style::Style::unref if it is not\n * used anymore. You do \\em not need to care about the reference counters\n * of fonts and styles.\n *\n * In detail:\n *\n * <ul>\n * <li> dw::core::style::Style::ref is called in\n *\n *      <ul>\n *      <li> dw::core::Widget::setStyle to assign a style to a widget,\n *      <li> dw::Textblock::addText, dw::Textblock::addWidget,\n *           dw::Textblock::addAnchor, dw::Textblock::addSpace,\n *           dw::Textblock::addParbreak and dw::Textblock::addLinebreak,\n *           to assign a style to a dw::Textblock::Word, and\n *      <li> by the HTML parser, when pushing an element on the stack.\n *      </ul>\n *\n * <li> dw::core::style::Style::unref is called in\n *\n *      <ul>\n *      <li> dw::core::Widget::~Widget, dw::Textblock::~Textblock, by the\n *           HTML parser, when popping an element fom the stack, and\n *      <li> dw::core::Widget::setStyle, dw::Textblock::addText etc.,\n *           these methods overwrite an existing style.\n *      </ul>\n * </ul>\n */\nnamespace style {\n\nenum Cursor {\n   CURSOR_CROSSHAIR,\n   CURSOR_DEFAULT,\n   CURSOR_POINTER,\n   CURSOR_MOVE,\n   CURSOR_E_RESIZE,\n   CURSOR_NE_RESIZE,\n   CURSOR_NW_RESIZE,\n   CURSOR_N_RESIZE,\n   CURSOR_SE_RESIZE,\n   CURSOR_SW_RESIZE,\n   CURSOR_S_RESIZE,\n   CURSOR_W_RESIZE,\n   CURSOR_TEXT,\n   CURSOR_WAIT,\n   CURSOR_HELP\n};\n\nenum BorderCollapse {\n   BORDER_MODEL_SEPARATE,\n   BORDER_MODEL_COLLAPSE\n};\n\nenum BorderStyle {\n   BORDER_NONE,\n   BORDER_HIDDEN,\n   BORDER_DOTTED,\n   BORDER_DASHED,\n   BORDER_SOLID,\n   BORDER_DOUBLE,\n   BORDER_GROOVE,\n   BORDER_RIDGE,\n   BORDER_INSET,\n   BORDER_OUTSET\n};\n\nenum BackgroundRepeat {\n   BACKGROUND_REPEAT,\n   BACKGROUND_REPEAT_X,\n   BACKGROUND_REPEAT_Y,\n   BACKGROUND_NO_REPEAT\n};\n\nenum BackgroundAttachment {\n   BACKGROUND_ATTACHMENT_SCROLL,\n   BACKGROUND_ATTACHMENT_FIXED\n};\n\nenum TextAlignType {\n   TEXT_ALIGN_LEFT,\n   TEXT_ALIGN_RIGHT,\n   TEXT_ALIGN_CENTER,\n   TEXT_ALIGN_JUSTIFY,\n   TEXT_ALIGN_STRING\n};\n\nenum VAlignType {\n   VALIGN_TOP,\n   VALIGN_BOTTOM,\n   VALIGN_MIDDLE,\n   VALIGN_BASELINE,\n   VALIGN_SUB,\n   VALIGN_SUPER,\n   VALIGN_TEXT_TOP,\n   VALIGN_TEXT_BOTTOM,\n};\n\nenum TextTransform {\n   TEXT_TRANSFORM_NONE,\n   TEXT_TRANSFORM_CAPITALIZE,\n   TEXT_TRANSFORM_UPPERCASE,\n   TEXT_TRANSFORM_LOWERCASE,\n};\n\n/**\n * \\todo Incomplete. Has to be completed for a CSS implementation.\n */\nenum DisplayType {\n   DISPLAY_BLOCK,\n   DISPLAY_INLINE,\n   DISPLAY_INLINE_BLOCK,\n   DISPLAY_LIST_ITEM,\n   DISPLAY_NONE,\n   DISPLAY_TABLE,\n   DISPLAY_TABLE_ROW_GROUP,\n   DISPLAY_TABLE_HEADER_GROUP,\n   DISPLAY_TABLE_FOOTER_GROUP,\n   DISPLAY_TABLE_ROW,\n   DISPLAY_TABLE_CELL\n};\n\nenum LineType {\n   LINE_NORMAL,\n   LINE_DOTTED,\n   LINE_DASHED\n};\n\nenum ListStylePosition {\n   LIST_STYLE_POSITION_INSIDE,\n   LIST_STYLE_POSITION_OUTSIDE\n};\n\nenum ListStyleType {\n   LIST_STYLE_TYPE_DISC,\n   LIST_STYLE_TYPE_CIRCLE,\n   LIST_STYLE_TYPE_SQUARE,\n   LIST_STYLE_TYPE_DECIMAL,\n   LIST_STYLE_TYPE_DECIMAL_LEADING_ZERO,\n   LIST_STYLE_TYPE_LOWER_ROMAN,\n   LIST_STYLE_TYPE_UPPER_ROMAN,\n   LIST_STYLE_TYPE_LOWER_GREEK,\n   LIST_STYLE_TYPE_LOWER_ALPHA,\n   LIST_STYLE_TYPE_LOWER_LATIN,\n   LIST_STYLE_TYPE_UPPER_ALPHA,\n   LIST_STYLE_TYPE_UPPER_LATIN,\n   LIST_STYLE_TYPE_HEBREW,\n   LIST_STYLE_TYPE_ARMENIAN,\n   LIST_STYLE_TYPE_GEORGIAN,\n   LIST_STYLE_TYPE_CJK_IDEOGRAPHIC,\n   LIST_STYLE_TYPE_HIRAGANA,\n   LIST_STYLE_TYPE_KATAKANA,\n   LIST_STYLE_TYPE_HIRAGANA_IROHA,\n   LIST_STYLE_TYPE_KATAKANA_IROHA,\n   LIST_STYLE_TYPE_NONE\n};\n\nenum FontStyle {\n  FONT_STYLE_NORMAL,\n  FONT_STYLE_ITALIC,\n  FONT_STYLE_OBLIQUE\n};\n\nenum FontVariant {\n   FONT_VARIANT_NORMAL,\n   FONT_VARIANT_SMALL_CAPS\n};\n\nenum Overflow {\n   OVERFLOW_VISIBLE,\n   OVERFLOW_HIDDEN,\n   OVERFLOW_SCROLL,\n   OVERFLOW_AUTO\n};\n\nenum Position {\n   POSITION_STATIC,\n   POSITION_RELATIVE,\n   POSITION_ABSOLUTE,\n   POSITION_FIXED,\n};\n\nenum TextDecoration {\n   TEXT_DECORATION_NONE         = 0,\n   TEXT_DECORATION_UNDERLINE    = 1 << 0,\n   TEXT_DECORATION_OVERLINE     = 1 << 1,\n   TEXT_DECORATION_LINE_THROUGH = 1 << 2,\n   TEXT_DECORATION_BLINK        = 1 << 3\n};\n\nenum WhiteSpace {\n   WHITE_SPACE_NORMAL,\n   WHITE_SPACE_PRE,\n   WHITE_SPACE_NOWRAP,\n   WHITE_SPACE_PRE_WRAP,\n   WHITE_SPACE_PRE_LINE,\n};\n\nenum FloatType {\n   FLOAT_NONE,\n   FLOAT_LEFT,\n   FLOAT_RIGHT\n};\n\nenum ClearType {\n   CLEAR_LEFT,\n   CLEAR_RIGHT,\n   CLEAR_BOTH,\n   CLEAR_NONE\n};\n\nenum {\n   /**\n    * \\brief 'z-index' is stored as int; use this for the value 'auto'.\n    *\n    * Only some random value, which has to be checked explicitly; do\n    * not compare this (less or greater) to integer values of\n    * 'z-index'.\n    */\n   Z_INDEX_AUTO = INT_MAX\n};\n\n/**\n * \\brief Type for representing all lengths within dw::core::style.\n *\n * Lengths are int's. Absolute lengths are represented in the following way:\n *\n * \\image html dw-style-length-absolute.png\n *\n * Percentages:\n *\n * \\image html dw-style-length-percentage.png\n *\n * Relative lengths (only used in HTML):\n *\n * \\image html dw-style-length-relative.png\n *\n * This is an implementation detail, use one of the following functions:\n *\n * Creating lengths:\n *\n * <ul>\n * <li> dw::core::style::createAbsLength\n * <li> dw::core::style::createPerLength\n * <li> dw::core::style::createRelLength\n * </ul>\n *\n * Examine lengths:\n *\n * <ul>\n * <li> dw::core::style::isAbsLength\n * <li> dw::core::style::isPerLength\n * <li> dw::core::style::isRelLength\n * <li> dw::core::style::absLengthVal\n * <li> dw::core::style::perLengthVal\n * <li> dw::core::style::relLengthVal\n * </ul>\n *\n * \"auto\" lengths are represented as dw::core::style::LENGTH_AUTO.\n */\ntypedef int Length;\n\n/** \\brief Returns a length of \\em n pixels. */\ninline Length createAbsLength(int n) { return (n << 2) | 1; }\n\n/** \\brief Returns a percentage, \\em v is relative to 1, not to 100. */\ninline Length createPerLength(double v) {\n   return ((int)(v * (1 << 18)) & ~3) | 2; }\n\n/** \\brief Returns a relative length. */\ninline Length createRelLength(double v) {\n   return ((int)(v * (1 << 18)) & ~3) | 3; }\n\n/** \\brief Returns true if \\em l is an absolute length. */\ninline bool isAbsLength(Length l) { return (l & 3) == 1; }\n\n/** \\brief Returns true if \\em l is a percentage. */\ninline bool isPerLength(Length l) { return (l & 3) == 2; }\n\n/** \\brief Returns true if \\em l is a relative length. */\ninline bool isRelLength(Length l) { return (l & 3) == 3; }\n\n/** \\brief Returns the value of a length in pixels, as an integer. */\ninline int absLengthVal(Length l) { return l >> 2; }\n\n/** \\brief Returns the value of a percentage, relative to 1, as a double.\n *\n * When possible, do not use this function directly; it may be removed\n * soon. Instead, use multiplyWithPerLength or multiplyWithPerLengthRounded.\n */\ninline double perLengthVal_useThisOnlyForDebugging(Length l)\n{ return (double)(l & ~3) / (1 << 18); }\n\n/** \\brief Returns the value of a relative length, as a float.\n *\n * When possible, do not use this function directly; it may be removed\n * soon.\n */\ninline double relLengthVal(Length l) { return (double)(l & ~3) / (1 << 18); }\n\n/**\n * \\brief Multiply an int with a percentage length, returning int.\n *\n * Use this instead of perLengthVal, when possible.\n */\ninline int multiplyWithPerLength(int x, Length l) {\n   return x * perLengthVal_useThisOnlyForDebugging (l);\n}\n\n/**\n * \\brief Like multiplyWithPerLength, but rounds to nearest integer\n *    instead of down.\n *\n * (This function exists for backward compatibility.)\n */\ninline int multiplyWithPerLengthRounded(int x, Length l) {\n   return lout::misc::roundInt (x * perLengthVal_useThisOnlyForDebugging (l));\n}\n\ninline int multiplyWithRelLength(int x, Length l) {\n   return x * relLengthVal(l);\n}\n\n\nenum {\n   /** \\brief Represents \"auto\" lengths. */\n   LENGTH_AUTO = 0\n};\n\n/**\n * \\brief Represents a dimension box according to the CSS box model.\n *\n * Used for dw::core::style::Style::margin,\n * dw::core::style::Style::borderWidth, and dw::core::style::Style::padding.\n */\nclass Box\n{\npublic:\n   /* in future also percentages */\n   int top, right, bottom, left;\n\n   inline void setVal(int val) { top = right = bottom = left = val; }\n   inline bool equals (Box *other) {\n      return top == other->top &&\n         right == other->right &&\n         bottom == other->bottom &&\n         left == other->left;\n   }\n   inline int hashValue () {\n      return top + right + bottom + left;\n   }\n};\n\nclass Tooltip;\nclass Font;\nclass Color;\nclass StyleImage;\n\n/**\n * \\sa dw::core::style\n */\nclass StyleAttrs : public lout::object::Object\n{\npublic:\n   Font *font;\n   int textDecoration; /* No TextDecoration because of problems converting\n                        * TextDecoration <-> int */\n   Color *color, *backgroundColor;\n   StyleImage *backgroundImage;\n   BackgroundRepeat backgroundRepeat;\n   BackgroundAttachment backgroundAttachment;\n   Length backgroundPositionX; // \"left\" defined by \"0%\" etc. (see CSS spec)\n   Length backgroundPositionY; // \"top\" defined by \"0%\" etc. (see CSS spec)\n\n   TextAlignType textAlign;\n   VAlignType valign;\n   char textAlignChar; /* In future, strings will be supported. */\n   TextTransform textTransform;\n\n   FloatType vloat; /* \"float\" is a keyword. */\n   ClearType clear;\n\n   Overflow overflow;\n\n   Position position;\n   Length top, bottom, left, right;\n\n   int hBorderSpacing, vBorderSpacing, wordSpacing;\n   Length width, height, lineHeight, textIndent;\n   Length minWidth, maxWidth, minHeight, maxHeight;\n\n   Box margin, borderWidth, padding;\n   BorderCollapse borderCollapse;\n   struct { Color *top, *right, *bottom, *left; } borderColor;\n   struct { BorderStyle top, right, bottom, left; } borderStyle;\n\n   DisplayType display;\n   WhiteSpace whiteSpace;\n   ListStylePosition listStylePosition;\n   ListStyleType listStyleType;\n   Cursor cursor;\n   int zIndex;\n\n   int x_link;\n   int x_img;\n   Tooltip *x_tooltip;\n   char x_lang[2]; /* Either x_lang[0] == x_lang[1] == 0 (no language\n                      set), or x_lang contains the RFC 1766 country\n                      code in lower case letters. (Only two letters\n                      allowed, currently.) */\n\n   void initValues ();\n   void resetValues ();\n\n   bool sizeDiffs (StyleAttrs *otherStyleAttrs);\n\n   inline void setBorderColor(Color *val) {\n      borderColor.top = borderColor.right = borderColor.bottom\n         = borderColor.left = val; }\n   inline void setBorderStyle(BorderStyle val) {\n      borderStyle.top = borderStyle.right = borderStyle.bottom\n         = borderStyle.left = val; }\n\n   inline int boxOffsetX ()\n   { return margin.left + borderWidth.left + padding.left; }\n   inline int boxRestWidth ()\n   { return margin.right + borderWidth.right + padding.right; }\n   inline int boxDiffWidth () { return boxOffsetX () + boxRestWidth (); }\n   inline int boxOffsetY ()\n   { return margin.top + borderWidth.top + padding.top; }\n   inline int boxRestHeight ()\n   { return margin.bottom + borderWidth.bottom + padding.bottom; }\n   inline int boxDiffHeight () { return boxOffsetY () + boxRestHeight (); }\n\n   inline bool hasBackground ()\n   { return backgroundColor != NULL || backgroundImage != NULL; }\n\n   bool equals (lout::object::Object *other);\n   int hashValue ();\n};\n\n\n/**\n * \\sa dw::core::style\n */\nclass Style: public StyleAttrs\n{\nprivate:\n   static int totalRef;\n   int refCount;\n   static lout::container::typed::HashTable <StyleAttrs, Style> *styleTable;\n\n   Style (StyleAttrs *attrs);\n\nprotected:\n   ~Style();\n\n   void copyAttrs (StyleAttrs *attrs);\n\npublic:\n   inline static Style *create (StyleAttrs *attrs)\n   {\n      Style *style = styleTable->get (attrs);\n      if (style) {\n         style->ref ();\n      } else {\n         style = new Style (attrs);\n         styleTable->put(style, style);\n      }\n      return style;\n   }\n\n   inline void ref () { refCount++; }\n   inline void unref () { if (--refCount == 0) delete this; }\n};\n\n\n/**\n * \\sa dw::core::style\n */\nclass TooltipAttrs: public lout::object::String\n{\npublic:\n   TooltipAttrs(const char *text): lout::object::String(text) { }\n};\n\n/**\n * \\sa dw::core::style\n */\nclass Tooltip: public TooltipAttrs\n{\nprivate:\n   int refCount;\n\nprotected:\n   Tooltip (const char *text): TooltipAttrs(text) { refCount = 0; }\n\npublic:\n   static Tooltip *create (dw::core::Layout *layout, const char *text);\n   inline void ref () { refCount++; }\n   inline void unref ()\n   { if (--refCount == 0) delete this; }\n\n   inline virtual void onEnter () { }\n   inline virtual void onLeave () { }\n   inline virtual void onMotion () { }\n};\n\n\n/**\n * \\sa dw::core::style\n */\nclass FontAttrs: public lout::object::Object\n{\npublic:\n   const char *name;\n   int size;\n   int weight;\n   int letterSpacing;\n   FontVariant fontVariant;\n   FontStyle style;\n\n   bool equals(lout::object::Object *other);\n   int hashValue();\n};\n\n\n/**\n * \\sa dw::core::style\n */\nclass Font: public FontAttrs\n{\nprivate:\n   int refCount;\n\n   static Font *create0 (Layout *layout, FontAttrs *attrs, bool tryEverything);\n\nprotected:\n   inline Font () {\n      DBG_OBJ_CREATE (\"dw::core::style::Font\");\n      refCount = 0;\n   }\n   virtual ~Font ();\n\n   void copyAttrs (FontAttrs *attrs);\n\npublic:\n   int ascent, descent;\n   int spaceWidth;\n   int xHeight;\n\n   static Font *create (Layout *layout, FontAttrs *attrs);\n   static bool exists (Layout *layout, const char *name);\n\n   inline void ref () { refCount++; }\n   inline void unref () { if (--refCount == 0) delete this; }\n};\n\n\n/**\n * \\sa dw::core::style\n */\nclass ColorAttrs: public lout::object::Object\n{\nprotected:\n   int color;\n\npublic:\n   inline ColorAttrs(int color)\n   {\n      this->color = color;\n   }\n\n   inline int getColor () { return color; }\n\n   bool equals(lout::object::Object *other);\n   int hashValue();\n};\n\n\n/**\n * \\sa dw::core::style\n */\nclass Color: public ColorAttrs\n{\nprivate:\n   int refCount;\n\n   void remove(dw::core::Layout *layout);\n   int shadeColor (int color, int d);\n\nprotected:\n   inline Color (int color): ColorAttrs (color) {\n      DBG_OBJ_CREATE (\"dw::core::style::Color\");\n      refCount = 0;\n   }\n   virtual ~Color ();\n\npublic:\n   enum Shading { SHADING_NORMAL, SHADING_INVERSE, SHADING_DARK, SHADING_LIGHT,\n                  SHADING_NUM };\n\nprotected:\n   int shadeColor (int color, Shading shading);\n\npublic:\n   static Color *create (Layout *layout, int color);\n\n   inline void ref () { refCount++; }\n   inline void unref ()\n   { if (--refCount == 0) delete this; }\n};\n\n\nclass StyleImage: public lout::signal::ObservedObject\n{\nprivate:\n   class StyleImgRenderer: public ImgRenderer\n   {\n   private:\n      StyleImage *image;\n\n   public:\n      inline StyleImgRenderer (StyleImage *image) { this->image = image; }\n\n      void setBuffer (core::Imgbuf *buffer, bool resize);\n      void drawRow (int row);\n      void finish ();\n      void fatal ();\n   };\n\n   int refCount, tilesX, tilesY;\n   Imgbuf *imgbufSrc, *imgbufTiled;\n   ImgRendererDist *imgRendererDist;\n   StyleImgRenderer *styleImgRenderer;\n\n   StyleImage ();\n   ~StyleImage ();\n\npublic:\n   /**\n    * \\brief Useful (but not mandatory) base class for updates of\n    *    areas with background images.\n    */\n   class ExternalImgRenderer: public ImgRenderer\n   {\n   public:\n      void setBuffer (core::Imgbuf *buffer, bool resize);\n      void drawRow (int row);\n      void finish ();\n      void fatal ();\n\n      /**\n       * \\brief If this method returns false, nothing is done at all.\n       */\n      virtual bool readyToDraw () = 0;\n\n      /**\n       * \\brief Return the area covered by the background image.\n       */\n      virtual void getBgArea (int *x, int *y, int *width, int *height) = 0;\n\n      /**\n       * \\brief Return the \"reference area\".\n       *\n       * See comment of \"drawBackground\".\n       */\n      virtual void getRefArea (int *xRef, int *yRef, int *widthRef,\n                               int *heightRef) = 0;\n\n      virtual StyleImage *getBackgroundImage () = 0;\n      virtual BackgroundRepeat getBackgroundRepeat () = 0;\n      virtual BackgroundAttachment getBackgroundAttachment () = 0;\n      virtual Length getBackgroundPositionX () = 0;\n      virtual Length getBackgroundPositionY () = 0;\n\n      /**\n       * \\brief Draw (or queue for drawing) an area, which is given in\n       *    canvas coordinates.\n       */\n      virtual void draw (int x, int y, int width, int height) = 0;\n   };\n\n   /**\n    * \\brief Suitable for widgets and parts of widgets.\n    */\n   class ExternalWidgetImgRenderer: public ExternalImgRenderer\n   {\n   public:\n      void getPaddingArea (int *x, int *y, int *width, int *height);\n\n      StyleImage *getBackgroundImage ();\n      BackgroundRepeat getBackgroundRepeat ();\n      BackgroundAttachment getBackgroundAttachment ();\n      Length getBackgroundPositionX ();\n      Length getBackgroundPositionY ();\n\n      /**\n       * \\brief Return the style this background image is part of.\n       */\n      virtual Style *getStyle () = 0;\n   };\n\n   static StyleImage *create () { return new StyleImage (); }\n\n   inline void ref () { refCount++; }\n   inline void unref ()\n   { if (--refCount == 0) delete this; }\n\n   inline Imgbuf *getImgbufSrc () { return imgbufSrc; }\n   inline Imgbuf *getImgbufTiled (bool repeatX, bool repeatY)\n   { return (imgbufTiled && repeatX && repeatY) ? imgbufTiled : imgbufSrc; }\n   inline int getTilesX (bool repeatX, bool repeatY)\n   { return (imgbufTiled && repeatX && repeatY) ? tilesX : 1; }\n   inline int getTilesY (bool repeatX, bool repeatY)\n   { return (imgbufTiled && repeatX && repeatY) ? tilesY : 1; }\n   inline ImgRenderer *getMainImgRenderer () { return imgRendererDist; }\n\n   /**\n    * \\brief Add an additional ImgRenderer, especially used for\n    *    drawing.\n    */\n   inline void putExternalImgRenderer (ImgRenderer *ir)\n   { imgRendererDist->put (ir); }\n\n   /**\n    * \\brief Remove a previously added additional ImgRenderer.\n    */\n   inline void removeExternalImgRenderer (ImgRenderer *ir)\n   { imgRendererDist->remove (ir); }\n};\n\nvoid drawBorder (View *view, Layout *layout, Rectangle *area,\n                 int x, int y, int width, int height,\n                 Style *style, bool inverse);\nvoid drawBackground (View *view, Layout *layout, Rectangle *area,\n                     int x, int y, int width, int height,\n                     int xRef, int yRef, int widthRef, int heightRef,\n                     Style *style, Color *bgColor, bool inverse, bool atTop);\nvoid drawBackgroundImage (View *view, StyleImage *backgroundImage,\n                          BackgroundRepeat backgroundRepeat,\n                          BackgroundAttachment backgroundAttachment,\n                          Length backgroundPositionX,\n                          Length backgroundPositionY,\n                          int x, int y, int width, int height,\n                          int xRef, int yRef, int widthRef, int heightRef);\nvoid numtostr (int num, char *buf, int buflen, ListStyleType listStyleType);\n\n} // namespace style\n} // namespace core\n} // namespace dw\n\n#endif // __DW_STYLE_HH__\n\n"
  },
  {
    "path": "dw/table.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007, 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n//#define DBG\n\n#include \"table.hh\"\n#include \"../lout/msg.h\"\n#include \"../lout/misc.hh\"\n#include \"../lout/debug.hh\"\n\nusing namespace lout;\n\nnamespace dw {\n\nbool Table::adjustTableMinWidth = true;\nint Table::CLASS_ID = -1;\n\nTable::Table(bool limitTextWidth)\n{\n   DBG_OBJ_CREATE (\"dw::Table\");\n   registerName (\"dw::Table\", &CLASS_ID);\n   setButtonSensitive(false);\n\n   this->limitTextWidth = limitTextWidth;\n\n   rowClosed = false;\n\n   numRows = 0;\n   numCols = 0;\n   curRow = -1;\n   curCol = 0;\n\n   DBG_OBJ_SET_NUM (\"numCols\", numCols);\n   DBG_OBJ_SET_NUM (\"numRows\", numCols);\n\n   children = new misc::SimpleVector <Child*> (16);\n   colExtremes = new misc::SimpleVector<core::Extremes> (8);\n   colWidthSpecified = new misc::SimpleVector<bool> (8);\n   colWidthPercentage = new misc::SimpleVector<bool> (8);\n   colWidths = new misc::SimpleVector <int> (8);\n   cumHeight = new misc::SimpleVector <int> (8);\n   rowSpanCells = new misc::SimpleVector <int> (8);\n   baseline = new misc::SimpleVector <int> (8);\n   rowStyle = new misc::SimpleVector <core::style::Style*> (8);\n\n   colWidthsUpToDateWidthColExtremes = true;\n   DBG_OBJ_SET_BOOL (\"colWidthsUpToDateWidthColExtremes\",\n                     colWidthsUpToDateWidthColExtremes);\n\n   numColWidthSpecified = 0;\n   numColWidthPercentage = 0;\n\n   redrawX = 0;\n   redrawY = 0;\n}\n\nTable::~Table()\n{\n   for (int i = 0; i < children->size (); i++) {\n      if (children->get(i)) {\n         switch (children->get(i)->type) {\n         case Child::CELL:\n            delete children->get(i)->cell.widget;\n            break;\n         case Child::SPAN_SPACE:\n            break;\n         }\n\n         delete children->get(i);\n      }\n   }\n\n   for (int i = 0; i < rowStyle->size (); i++)\n      if (rowStyle->get (i))\n         rowStyle->get(i)->unref ();\n\n   delete children;\n   delete colExtremes;\n   delete colWidthSpecified;\n   delete colWidthPercentage;\n   delete colWidths;\n   delete cumHeight;\n   delete rowSpanCells;\n   delete baseline;\n   delete rowStyle;\n\n   DBG_OBJ_DELETE ();\n}\n\nvoid Table::sizeRequestSimpl (core::Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequestImpl\");\n\n   forceCalcCellSizes (true);\n\n   /**\n    * \\bug Baselines are not regarded here.\n    */\n   requisition->width =\n      boxDiffWidth () + (numCols + 1) * getStyle()->hBorderSpacing;\n   for (int col = 0; col < numCols; col++)\n      requisition->width += colWidths->get (col);\n\n   requisition->ascent =\n      boxDiffHeight () + cumHeight->get (numRows) + getStyle()->vBorderSpacing;\n   requisition->descent = 0;\n\n   correctRequisition (requisition, core::splitHeightPreserveDescent, true,\n                       false);\n\n   // For the order, see similar reasoning for dw::Textblock.\n   correctRequisitionByOOF (requisition, core::splitHeightPreserveDescent);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Table::getExtremesSimpl (core::Extremes *extremes)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"getExtremesImpl\");\n\n   if (numCols == 0)\n      extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth =\n         extremes->maxWidthIntrinsic = extremes->adjustmentWidth =\n         boxDiffWidth ();\n   else {\n      forceCalcColumnExtremes ();\n\n      extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth =\n         extremes->maxWidthIntrinsic = extremes->adjustmentWidth =\n         (numCols + 1) * getStyle()->hBorderSpacing + boxDiffWidth ();\n      for (int col = 0; col < numCols; col++) {\n         extremes->minWidth += colExtremes->getRef(col)->minWidth;\n         extremes->minWidthIntrinsic +=\n            colExtremes->getRef(col)->minWidthIntrinsic;\n         extremes->maxWidth += colExtremes->getRef(col)->maxWidth;\n         extremes->maxWidthIntrinsic +=\n            colExtremes->getRef(col)->maxWidthIntrinsic;\n         extremes->adjustmentWidth += colExtremes->getRef(col)->adjustmentWidth;\n      }\n   }\n\n   correctExtremes (extremes, true);\n\n   // For the order, see similar reasoning for dw::Textblock.\n   correctExtremesByOOF (extremes);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Table::sizeAllocateImpl (core::Allocation *allocation)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"sizeAllocateImpl\", \"%d, %d; %d * (%d + %d)\",\n                  allocation->x, allocation->y, allocation->width,\n                  allocation->ascent, allocation->descent);\n\n   sizeAllocateStart (allocation);\n\n   calcCellSizes (true);\n\n   /**\n    * \\bug Baselines are not regarded here.\n    */\n\n   int offy = allocation->y + boxOffsetY () + getStyle()->vBorderSpacing;\n   int x = allocation->x + boxOffsetX () + getStyle()->hBorderSpacing;\n\n   for (int col = 0; col < numCols; col++) {\n      for (int row = 0; row < numRows; row++) {\n         int n = row * numCols + col;\n         if (childDefined (n)) {\n            int width = (children->get(n)->cell.colspanEff - 1)\n               * getStyle()->hBorderSpacing;\n            for (int i = 0; i < children->get(n)->cell.colspanEff; i++)\n               width += colWidths->get (col + i);\n\n            core::Allocation childAllocation;\n            core::Requisition childRequisition;\n\n            children->get(n)->cell.widget->sizeRequest (&childRequisition);\n\n            childAllocation.x = x;\n            childAllocation.y = cumHeight->get (row) + offy;\n            childAllocation.width = width;\n            childAllocation.ascent = childRequisition.ascent;\n            childAllocation.descent =\n               cumHeight->get (row + children->get(n)->cell.rowspan)\n               - cumHeight->get (row) - getStyle()->vBorderSpacing\n               - childRequisition.ascent;\n            children->get(n)->cell.widget->sizeAllocate (&childAllocation);\n         }\n      }\n\n      x += colWidths->get (col) + getStyle()->hBorderSpacing;\n   }\n\n   sizeAllocateEnd ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Table::resizeDrawImpl ()\n{\n   queueDrawArea (redrawX, 0, allocation.width - redrawX, getHeight ());\n   queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY);\n   redrawX = allocation.width;\n   redrawY = getHeight ();\n}\n\nint Table::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"getAvailWidthOfChild\", \"%p, %s\",\n                  child, forceValue ? \"true\" : \"false\");\n\n   int width;\n   oof::OutOfFlowMgr *oofm;\n\n   if (isWidgetOOF(child) && (oofm = getWidgetOutOfFlowMgr(child)) &&\n       oofm->dealingWithSizeOfChild (child))\n      width = oofm->getAvailWidthOfChild (child, forceValue);\n   else {\n      // We do not calculate the column widths at this point, because\n      // this tends to be rather inefficient for tables with many\n      // cells:\n      //\n      // For each of the n cells, some text is added (say, only one word\n      // per cell). Textblock::addText will eventually (via addText0\n      // etc.) call this method, Table::getAvailWidthOfChild. If\n      // calcCellSizes() is called here, this will call\n      // forceCalcCellSizes(), since the last call, sizes have to be\n      // re-calculated (because cells have been added). This will\n      // calculate the extremes for each existing cell, so\n      // Widget::getExtremes is called n * (n + 1) / 2 times. Even if the\n      // extremes are cached (so that getExtremesImpl does not have to be\n      // called in each case), this would make rendering tables with more\n      // than a few hundred cells unacceptably slow.\n      //\n      // Instead, column widths are calculated in Table::sizeRequestImpl.\n      //\n      // An alternative would be incremental resizing for tables; this\n      // approach resembles the behaviour before GROWS.\n\n      // TODO Does it still make sence to return -1 when forceValue is\n      // set?\n      if (forceValue)\n         width = calcAvailWidthForDescendant (child);\n      else\n         width = -1;\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"=> %d\", width);\n   DBG_OBJ_LEAVE ();\n   return width;\n}\n\nint Table::calcAvailWidthForDescendant (Widget *child)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcAvailWidthForDescendant\", \"%p\", child);\n\n   // \"child\" is not a direct child, but a direct descendant. Search\n   // for the actual childs.\n   Widget *actualChild = child;\n   while (actualChild != NULL && actualChild->getParent () != this)\n      actualChild = actualChild->getParent ();\n\n   assert (actualChild != NULL);\n\n   // ActualChild->parentRef contains (indirectly) the position in the\n   // children array (see addCell()), so the column can be easily\n   // determined.\n   int childNo = getParentRefInFlowSubRef (actualChild->parentRef);\n   int col = childNo % numCols;\n   DBG_OBJ_MSGF (\"resize\", 1, \"actualChild = %p, \"\n                 \"childNo = getParentRefInFlowSubRef (%d) = %d, \"\n                 \"column = %d %% %d = %d\",\n                 actualChild, actualChild->parentRef, childNo, childNo,\n                 numCols, col);\n   int colspanEff = children->get(childNo)->cell.colspanEff;\n   DBG_OBJ_MSGF (\"resize\", 1, \"calculated from column %d, colspanEff = %d\",\n                 col, colspanEff);\n\n   int width = (colspanEff - 1) * getStyle()->hBorderSpacing;\n   for (int i = 0; i < colspanEff; i++)\n      width += colWidths->get (col + i);\n   width = misc::max (width, 0);\n\n   if (child != actualChild) {\n      // For table cells (direct children: child == actualChild), CSS\n      // 'width' is already regarded in the column calculation.\n      // However, for children of the table cells, CSS 'width' must be\n      // regarded here.\n\n      int corrWidth = width;\n      child->calcFinalWidth (child->getStyle(), -1, this, 0, true, &corrWidth);\n      \n      // But better not exceed it ... (TODO: Only here?)\n      width = misc::min (width, corrWidth);\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"=> %d\", width);\n   DBG_OBJ_LEAVE ();\n   return width;\n}\n\nint Table::applyPerWidth (int containerWidth, core::style::Length perWidth)\n{\n   return core::style::multiplyWithPerLength (containerWidth, perWidth);\n}\n\nint Table::applyPerHeight (int containerHeight, core::style::Length perHeight)\n{\n   return core::style::multiplyWithPerLength (containerHeight, perHeight);\n}\n\nvoid Table::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n\n   for (int col = 0; col < numCols; col++) {\n      for (int row = 0; row < numRows; row++) {\n         int n = row * numCols + col;\n         if (childDefined (n))\n            children->get(n)->cell.widget->containerSizeChanged ();\n      }\n   }\n\n   containerSizeChangedForChildrenOOF ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nbool Table::affectsSizeChangeContainerChild (core::Widget *child)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"affectsSizeChangeContainerChild\", \"%p\", child);\n\n   bool ret;\n\n   // This is a bit more complicated, as compared to the standard\n   // implementation (Widget::affectsSizeChangeContainerChild).\n   // Height would handled the same way, but width is more\n   // complicated: we would have to track numerous values here. Always\n   // returning true is correct in all cases, but generally\n   // inefficient.\n\n   // TODO Better solution?\n\n   ret = true;\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"=> %s\", ret ? \"true\" : \"false\");\n   DBG_OBJ_LEAVE ();\n   return ret;\n}\n\nbool Table::usesAvailWidth ()\n{\n   return true;\n}\n\nbool Table::isBlockLevel ()\n{\n   return true;\n}\n\nvoid Table::drawLevel (core::View *view, core::Rectangle *area, int level,\n                       core::DrawingContext *context)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"Table::drawLevel\", \"[%d, %d, %d * %d], %s\",\n                  area->x, area->y, area->width, area->height,\n                  stackingLevelText (level));\n\n#if 0\n   // This old code belongs perhaps to the background. Check when reactivated.\n   int offx = getStyle()->boxOffsetX () + getStyle()->hBorderSpacing;\n   int offy = getStyle()->boxOffsetY () + getStyle()->vBorderSpacing;\n   int width = getContentWidth ();\n   \n   // This part seems unnecessary. It also segfaulted sometimes when\n      // cumHeight size was less than numRows. --jcid\n   for (int row = 0; row < numRows; row++) {\n      if (rowStyle->get (row))\n         drawBox (view, rowStyle->get (row), area,\n                  offx, offy + cumHeight->get (row),\n                  width - 2*getStyle()->hBorderSpacing,\n                  cumHeight->get (row + 1) - cumHeight->get (row)\n                  - getStyle()->vBorderSpacing, false);\n   }\n#endif\n\n   switch (level) {\n   case SL_IN_FLOW:\n      for (int i = 0; i < children->size (); i++) {\n         if (childDefined (i)) {\n            Widget *child = children->get(i)->cell.widget;\n            core::Rectangle childArea;\n            if (!core::StackingContextMgr::handledByStackingContextMgr (child)\n                && child->intersects (this, area, &childArea))\n               child->draw (view, &childArea, context);\n         }\n      }\n      break;\n\n   default:\n      OOFAwareWidget::drawLevel (view, area, level, context);\n      break;\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\ncore::Widget *Table::getWidgetAtPointLevel (int x, int y, int level,\n                                            core::GettingWidgetAtPointContext\n                                            *context)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"Table::getWidgetAtPointLevel\", \"%d, %d, %s\",\n                  x, y, stackingLevelText (level));\n\n   Widget *widgetAtPoint = NULL;\n\n   switch (level) {\n   case SL_IN_FLOW:\n      for (int i = children->size () - 1; widgetAtPoint == NULL && i >= 0;\n           i--) {\n         if (childDefined (i)) {\n            Widget *child = children->get(i)->cell.widget;\n            if (!core::StackingContextMgr::handledByStackingContextMgr (child))\n               widgetAtPoint = child->getWidgetAtPoint (x, y, context);\n         }\n      }\n      break;\n\n   default:\n      widgetAtPoint =\n         OOFAwareWidget::getWidgetAtPointLevel (x, y, level, context);\n      break;\n   }\n\n   DBG_OBJ_MSGF (\"events\", 1, \"=> %p\", widgetAtPoint);\n   DBG_OBJ_LEAVE ();\n\n   return widgetAtPoint;\n}\n\nvoid Table::removeChild (Widget *child)\n{\n   /** \\bug Not implemented. */\n}\n\ncore::Iterator *Table::iterator (core::Content::Type mask, bool atEnd)\n{\n   return new TableIterator (this, mask, atEnd);\n}\n\nvoid Table::addCell (Widget *widget, int colspan, int rowspan)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"addCell\", \"%p, %d, %d\",\n                  widget, colspan, rowspan);\n\n   const int maxspan = 100;\n   Child *child;\n   int colspanEff;\n\n   // We limit the values for colspan and rowspan to avoid\n   // attacks by malicious web pages.\n   if (colspan > maxspan || colspan < 0) {\n      MSG_WARN(\"colspan = %d is set to %d.\\n\", colspan, maxspan);\n      colspan = maxspan;\n   }\n   if (rowspan > maxspan || rowspan <= 0) {\n      MSG_WARN(\"rowspan = %d is set to %d.\\n\", rowspan, maxspan);\n      rowspan = maxspan;\n   }\n\n   if (numRows == 0) {\n      // to prevent a crash\n      MSG(\"addCell: cell without row.\\n\");\n      addRow (NULL);\n   }\n\n   if (rowClosed) {\n      MSG_WARN(\"Last cell had colspan=0.\\n\");\n      addRow (NULL);\n   }\n\n   if (colspan == 0) {\n      colspanEff = misc::max (numCols - curCol, 1);\n      rowClosed = true;\n   } else\n      colspanEff = colspan;\n\n   // Find next free cell-\n   while (curCol < numCols &&\n          (child = children->get(curRow * numCols + curCol)) != NULL &&\n          child->type == Child::SPAN_SPACE)\n      curCol++;\n\n   _MSG(\"Table::addCell numCols=%d,curCol=%d,colspan=%d,colspanEff=%d\\n\",\n       numCols, curCol, colspan, colspanEff);\n\n   // Increase children array, when necessary.\n   if (curRow + rowspan > numRows)\n      reallocChildren (numCols, curRow + rowspan);\n   if (curCol + colspanEff > numCols)\n      reallocChildren (curCol + colspanEff, numRows);\n\n   // Fill span space.\n   for (int col = 0; col < colspanEff; col++)\n      for (int row = 0; row < rowspan; row++)\n         if (!(col == 0 && row == 0)) {\n            int i = (curRow + row) * numCols + curCol + col;\n\n            child = children->get(i);\n            if (child) {\n               MSG(\"Overlapping spans in table.\\n\");\n               assert(child->type == Child::SPAN_SPACE);\n               delete child;\n            }\n            child = new Child ();\n            child->type = Child::SPAN_SPACE;\n            child->spanSpace.startCol = curCol;\n            child->spanSpace.startRow = curRow;\n            children->set (i, child);\n         }\n\n   // Set the \"root\" cell.\n   child = new Child ();\n   child->type = Child::CELL;\n   child->cell.widget = widget;\n   child->cell.colspanOrig = colspan;\n   child->cell.colspanEff = colspanEff;\n   child->cell.rowspan = rowspan;\n   children->set (curRow * numCols + curCol, child);\n\n   // The position in the children array is (indirectly) assigned to parentRef,\n   // although incremental resizing is not implemented. Useful, e. g., in\n   // calcAvailWidthForDescendant(). See also reallocChildren().\n   widget->parentRef = makeParentRefInFlow (curRow * numCols + curCol);\n   DBG_OBJ_SET_NUM_O (widget, \"parentRef\", widget->parentRef);\n\n   curCol += colspanEff;\n   \n   widget->setParent (this);\n   if (rowStyle->get (curRow))\n      widget->setBgColor (rowStyle->get(curRow)->backgroundColor);\n   queueResize (0, true);\n\n#if 0\n   // show table structure in stdout\n   for (int row = 0; row < numRows; row++) {\n      for (int col = 0; col < numCols; col++) {\n         int n = row * numCols + col;\n         if (!(child = children->get (n))) {\n            MSG(\"[null     ] \");\n         } else if (children->get(n)->type == Child::CELL) {\n            MSG(\"[CELL rs=%d] \", child->cell.rowspan);\n         } else if (children->get(n)->type == Child::SPAN_SPACE) {\n            MSG(\"[SPAN rs=%d] \", child->cell.rowspan);\n         } else {\n            MSG(\"[Unk.     ] \");\n         }\n      }\n      MSG(\"\\n\");\n   }\n   MSG(\"\\n\");\n#endif\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Table::addRow (core::style::Style *style)\n{\n   curRow++;\n\n   if (curRow >= numRows)\n      reallocChildren (numCols, curRow + 1);\n\n   if (rowStyle->get (curRow))\n      rowStyle->get(curRow)->unref ();\n\n   rowStyle->set (curRow, style);\n   if (style)\n      style->ref ();\n\n   curCol = 0;\n   rowClosed = false;\n}\n\nAlignedTableCell *Table::getCellRef ()\n{\n   core::Widget *child;\n\n   for (int row = 0; row <= numRows; row++) {\n      int n = curCol + row * numCols;\n      if (childDefined (n)) {\n         child = children->get(n)->cell.widget;\n         if (child->instanceOf (AlignedTableCell::CLASS_ID))\n            return (AlignedTableCell*)child;\n      }\n   }\n\n   return NULL;\n}\n\nconst char *Table::getExtrModName (ExtrMod mod)\n{\n   switch (mod) {\n   case MIN:\n      return \"MIN\";\n\n   case MIN_INTR:\n      return \"MIN_INTR\";\n\n   case MIN_MIN:\n      return \"MIN_MIN\";\n\n   case MAX_MIN:\n      return \"MAX_MIN\";\n\n   case MAX:\n      return \"MAX\";\n\n   case MAX_INTR:\n      return \"MAX_INTR\";\n\n   case DATA:\n      return \"DATA\";\n\n   default:\n      misc::assertNotReached ();\n      return NULL;\n   }\n}\n\nint Table::getExtreme (core::Extremes *extremes, ExtrMod mod)\n{\n   switch (mod) {\n   case MIN:\n      return extremes->minWidth;\n\n   case MIN_INTR:\n      return extremes->minWidthIntrinsic;\n\n   case MIN_MIN:\n      return misc::min (extremes->minWidth, extremes->minWidthIntrinsic);\n\n   case MAX_MIN:\n      return misc::max (extremes->minWidth, extremes->minWidthIntrinsic);\n\n   case MAX:\n      return extremes->maxWidth;\n\n   case MAX_INTR:\n      return extremes->maxWidthIntrinsic;\n\n   default:\n      misc::assertNotReached ();\n      return 0;\n   }\n}\n\nvoid Table::setExtreme (core::Extremes *extremes, ExtrMod mod, int value)\n{\n   switch (mod) {\n   case MIN:\n      extremes->minWidth = value;\n      break;\n\n   case MIN_INTR:\n      extremes->minWidthIntrinsic = value;\n      break;\n\n   // MIN_MIN and MAX_MIN not supported here.\n\n   case MAX:\n      extremes->maxWidth = value;\n      break;\n\n   case MAX_INTR:\n      extremes->maxWidthIntrinsic = value;\n      break;\n\n   default:\n      misc::assertNotReached ();\n   }\n}\n\nint Table::getColExtreme (int col, ExtrMod mod, void *data)\n{\n   switch (mod) {\n   case DATA:\n      return ((misc::SimpleVector<int>*)data)->get (col);\n\n   default:\n      return getExtreme (colExtremes->getRef(col), mod);\n   }\n}\n\nvoid Table::setColExtreme (int col, ExtrMod mod, void *data, int value)\n{\n   switch (mod) {\n   case DATA:\n      ((misc::SimpleVector<int>*)data)->set (col, value);\n\n   default:\n      setExtreme (colExtremes->getRef(col), mod, value);\n   }\n}\n\nvoid Table::reallocChildren (int newNumCols, int newNumRows)\n{\n   assert (newNumCols >= numCols);\n   assert (newNumRows >= numRows);\n\n   children->setSize (newNumCols * newNumRows);\n\n   if (newNumCols > numCols) {\n      // Complicated case, array got also wider.\n      for (int row = newNumRows - 1; row >= 0; row--) {\n         int colspan0Col = -1, colspan0Row = -1;\n\n         // Copy old part.\n         for (int col = numCols - 1; col >= 0; col--) {\n            int n = row * newNumCols + col;\n            children->set (n, children->get (row * numCols + col));\n            if (children->get (n)) {\n               switch (children->get(n)->type) {\n               case Child::CELL:\n                  if (children->get(n)->cell.colspanOrig == 0) {\n                     colspan0Col = col;\n                     colspan0Row = row;\n                     children->get(n)->cell.colspanEff = newNumCols - col;\n                  }\n                  break;\n               case Child::SPAN_SPACE:\n                  if (children->get(children->get(n)->spanSpace.startRow\n                                    * numCols +\n                                    children->get(n)->spanSpace.startCol)\n                      ->cell.colspanOrig == 0) {\n                     colspan0Col = children->get(n)->spanSpace.startCol;\n                     colspan0Row = children->get(n)->spanSpace.startRow;\n                  }\n                  break;\n               }\n            }\n         }\n\n         // Fill rest of the column.\n         if (colspan0Col == -1) {\n            for (int col = numCols; col < newNumCols; col++)\n               children->set (row * newNumCols + col, NULL);\n         } else {\n            for (int col = numCols; col < newNumCols; col++) {\n               Child *child = new Child ();\n               child->type = Child::SPAN_SPACE;\n               child->spanSpace.startCol = colspan0Col;\n               child->spanSpace.startRow = colspan0Row;\n               children->set (row * newNumCols + col, child);\n            }\n         }\n      }\n   }\n\n   // Bottom part of the children array.\n   for (int row = numRows; row < newNumRows; row++)\n      for (int col = 0; col < newNumCols; col++)\n         children->set (row * newNumCols + col, NULL);\n\n   // Simple arrays.\n   rowStyle->setSize (newNumRows);\n   for (int row = numRows; row < newNumRows; row++)\n      rowStyle->set (row, NULL);\n   // Rest is increased, when needed.\n\n   if (newNumCols > numCols) {\n      // Re-calculate parentRef. See addCell().\n      for (int row = 1; row < newNumRows; row++)\n         for (int col = 0; col < newNumCols; col++) {\n            int n = row * newNumCols + col;\n            Child *child = children->get (n);\n            if (child != NULL && child->type == Child::CELL) {\n               child->cell.widget->parentRef = makeParentRefInFlow (n);\n               DBG_OBJ_SET_NUM_O (child->cell.widget, \"parentRef\",\n                                  child->cell.widget->parentRef);\n            }\n         }\n   }\n\n   numCols = newNumCols;\n   numRows = newNumRows;\n\n   // We initiate the column widths with a random value, to have a\n   // defined available width for the children before the column\n   // widths are actually calculated.\n\n   colWidths->setSize (numCols, 100);\n\n   DBG_IF_RTFL {\n      DBG_OBJ_SET_NUM (\"colWidths.size\", colWidths->size ());\n      for (int i = 0; i < colWidths->size (); i++)\n         DBG_OBJ_ARRSET_NUM (\"colWidths\", i, colWidths->get (i));\n   }\n\n   DBG_OBJ_SET_NUM (\"numCols\", numCols);\n   DBG_OBJ_SET_NUM (\"numRows\", numCols);\n}\n\n// ----------------------------------------------------------------------\n\nvoid Table::calcCellSizes (bool calcHeights)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcCellSizes\", \"%s\",\n                  calcHeights ? \"true\" : \"false\");\n\n   bool sizeChanged = needsResize () || resizeQueued ();\n   bool extremesChanget = extremesChanged () || extremesQueued ();\n\n   if (calcHeights ? (extremesChanget || sizeChanged) :\n       (extremesChanget || !colWidthsUpToDateWidthColExtremes))\n      forceCalcCellSizes (calcHeights);\n\n   DBG_OBJ_LEAVE ();\n}\n\n\nvoid Table::forceCalcCellSizes (bool calcHeights)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"forceCalcCellSizes\", \"%s\",\n                  calcHeights ? \"true\" : \"false\");\n\n   // Since Table::getAvailWidthOfChild does not calculate the column\n   // widths, and so initially a random value (100) is returned, a\n   // correction is necessary. The old values are temporary preserved\n   // ...\n\n   lout::misc::SimpleVector<int> oldColWidths (8);\n   oldColWidths.setSize (colWidths->size ());\n   colWidths->copyTo (&oldColWidths);\n   \n   actuallyCalcCellSizes (calcHeights);\n\n   // ... and then compared to the new ones. In case of a difference,\n   // the cell is told about this.\n\n   for (int col = 0; col < colWidths->size (); col++) {\n      if (oldColWidths.get (col) != colWidths->get (col)) {\n         for (int row = 0; row < numRows; row++) {\n            int n = row * numCols + col, col2;\n            Child *child = children->get(n);\n            if (child) {\n               Widget *cell;\n               switch (child->type) {\n               case Child::CELL:\n                  cell = child->cell.widget;\n                  break;\n\n               case Child::SPAN_SPACE:\n                  // TODO Are Child::spanSpace::startRow and\n                  // Child::spanSpace::startCol not defined?\n\n                  // Search for actual cell. If not found, this means\n                  // that a cell is spanning multiple columns *and*\n                  // rows; in this case it has been processed before.\n\n                  cell = NULL;\n                  for (col2 = col - 1; col2 >= 0 && cell == NULL; col2--) {\n                     int n2 = row * numCols + col2;\n                     Child *child2 = children->get(n2);\n                     if (child2 != NULL && child2->type == Child::CELL)\n                        cell = child2->cell.widget;\n                  }\n                  break;\n\n               default:\n                  misc::assertNotReached ();\n                  cell = NULL;\n               }\n                  \n               if (cell)\n                  cell->containerSizeChanged ();\n            }\n         }\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Table::actuallyCalcCellSizes (bool calcHeights)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"actuallyCalcCellSizes\", \"%s\",\n                  calcHeights ? \"true\" : \"false\");\n\n   int childHeight;\n   core::Extremes extremes;\n\n   // Will also call forceCalcColumnExtremes(), when needed.\n   getExtremes (&extremes);\n\n   int availWidth = getAvailWidth (true);\n   // When adjust_table_min_width is set, use perhaps the adjustment\n   // width for correction. (TODO: Is this necessary?)\n   int corrWidth =\n      Table::getAdjustTableMinWidth () ? extremes.adjustmentWidth : 0;\n   int totalWidth = misc::max (availWidth, corrWidth)\n      - ((numCols + 1) * getStyle()->hBorderSpacing + boxDiffWidth ());\n      \n   DBG_OBJ_MSGF (\"resize\", 1,\n                 \"totalWidth = max (%d, %d) - ((%d - 1) * %d + %d) = <b>%d</b>\",\n                 availWidth, corrWidth, numCols, getStyle()->hBorderSpacing,\n                 boxDiffWidth (), totalWidth);\n\n   assert (colWidths->size () == numCols); // This is set in addCell.\n   cumHeight->setSize (numRows + 1, 0);\n   rowSpanCells->setSize (0);\n   baseline->setSize (numRows);\n\n   misc::SimpleVector<int> *oldColWidths = colWidths;\n   colWidths = new misc::SimpleVector <int> (8);\n   colWidths->setSize (numCols);\n\n   int minWidth = 0, minWidthIntrinsic = 0, maxWidth = 0;\n   for (int col = 0; col < colExtremes->size(); col++) {\n      minWidth += colExtremes->getRef(col)->minWidth;\n      minWidthIntrinsic += colExtremes->getRef(col)->minWidthIntrinsic;\n      maxWidth += colExtremes->getRef(col)->maxWidth;\n   }\n\n   // CSS 'width' defined and effective?\n   bool totalWidthSpecified = false;\n   if (getStyle()->width != core::style::LENGTH_AUTO) {\n      // Even if 'width' is defined, it may not have a defined value. We try\n      // this trick (should perhaps be replaced by a cleaner solution):\n      core::Requisition testReq = { -1, -1, -1 };\n      correctRequisition (&testReq, core::splitHeightPreserveDescent, true,\n                          false);\n      if (testReq.width != -1)\n         totalWidthSpecified = true;\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1,\n                 \"minWidth = %d, minWidthIntrinsic = %d, maxWidth %d, \"\n                 \"totalWidth = %d, %s\",\n                 minWidth, minWidthIntrinsic, maxWidth, totalWidth,\n                 totalWidthSpecified ? \"specified\" : \"not specified\");\n\n   if (minWidth > totalWidth) {\n      DBG_OBJ_MSG (\"resize\", 1, \"case 1: minWidth > totalWidth\");\n\n      // The sum of all column minima is larger than the available\n      // width, so we narrow the columns (see also CSS2 spec,\n      // section 17.5, #6). We use a similar apportioning, but not\n      // bases on minimal and maximal widths, but on intrinsic minimal\n      // widths and corrected minimal widths. This way, intrinsic\n      // extremes are preferred (so avoiding columns too narrow for\n      // the actual contents), at the expenses of corrected ones\n      // (which means that sometimes CSS values are handled\n      // incorrectly).\n\n      // A special case is a table with columns whose widths are\n      // defined by percentage values. In this case, all other columns\n      // are applied the intrinsic minimal width, while larger values\n      // are applied to the columns with percentage width (but not\n      // larger than the corrected width). The left columns are\n      // preferred, but it is ensured that no column is narrower than\n      // the intrinsic minimum.\n      //\n      // Example two columns with both \"width: 70%\" will be displayed like\n      // this:\n      //\n      // --------------------------------------------------\n      // |                                 |              |\n      // --------------------------------------------------\n      //\n      // The first gets indeed 70% of the total width, the second only\n      // the rest.\n      //\n      // This somewhat strange behaviour tries to mimic the somewhat\n      // strange behaviour of Firefox and Chromium.\n\n      if (numColWidthPercentage == 0 || minWidthIntrinsic >= totalWidth) {\n         // Latter case (minWidthIntrinsic >= totalWidth): special treating\n         // of percentage values would not make sense.\n\n         DBG_OBJ_MSG (\"resize\", 1, \"case 1a: simple apportioning\");\n\n         apportion2 (totalWidth, 0, colExtremes->size() - 1, MIN_MIN, MAX_MIN,\n                     NULL, colWidths, 0);\n      } else {\n         DBG_OBJ_MSG (\"resize\", 1, \"case 1b: treat percentages specially\");\n\n         // Keep track of the width which is apportioned to the rest\n         // of the columns with percentage width (widthPartPer), and\n         // the minimal width (intrinsic minimum) which is needed for\n         // the rest of these columns (minWidthIntrinsicPer).\n\n         int widthPartPer = totalWidth, minWidthIntrinsicPer = 0;\n         for (int col = 0; col < colExtremes->size(); col++)\n            if (colWidthPercentage->get (col))\n               minWidthIntrinsicPer +=\n                  colExtremes->getRef(col)->minWidthIntrinsic;\n            else\n               // Columns without percentage width get only the\n               // intrinsic mininal, so subtract this from the width for the\n               // columns *with* percentage\n               widthPartPer -=\n                  colExtremes->getRef(col)->minWidthIntrinsic;\n\n         DBG_OBJ_MSGF (\"resize\", 1,\n                       \"widthPartPer = %d, minWidthIntrinsicPer = %d\",\n                       widthPartPer, minWidthIntrinsicPer);\n\n         for (int col = 0; col < colExtremes->size(); col++)\n            if (colWidthPercentage->get (col)) {\n               int colWidth = colExtremes->getRef(col)->minWidth;\n               int minIntr = colExtremes->getRef(col)->minWidthIntrinsic;\n\n               minWidthIntrinsicPer -= minIntr;\n\n               if (colWidth > widthPartPer - minWidthIntrinsicPer)\n                  colWidth = widthPartPer - minWidthIntrinsicPer;\n\n               colWidths->set (col, colWidth);\n               widthPartPer -= colWidth;\n\n               DBG_OBJ_MSGF (\"resize\", 1,\n                             \"#%d: colWidth = %d ... widthPartPer = %d, \"\n                             \"minWidthIntrinsicPer = %d\",\n                             col, colWidth, widthPartPer, minWidthIntrinsicPer);\n\n            } else\n               colWidths->set (col,\n                               colExtremes->getRef(col)->minWidthIntrinsic);\n\n      }\n   } else if (totalWidthSpecified && totalWidth > maxWidth) {\n      DBG_OBJ_MSG (\"resize\", 1,\n                   \"case 2: totalWidthSpecified && totalWidth > maxWidth\");\n\n      // The width is specified (and so enforced), but all maxima sum\n      // up to less than this specified width. The columns will have\n      // there maximal width, and the extra space is apportioned\n      // according to the column widths, and so to the column\n      // maxima. This is done by simply passing MAX twice to the\n      // apportioning function.\n\n      // When column widths are specified (numColWidthSpecified > 0,\n      // as calculated in forceCalcColumnExtremes()), they are treated\n      // specially and excluded from the apportioning, so that the\n      // specified column widths are enforced. An exception is when\n      // all columns are specified: in this case they must be\n      // enlargened to fill the whole table width.\n\n      if (numColWidthSpecified == 0 ||\n          numColWidthSpecified == colExtremes->size()) {\n         DBG_OBJ_MSG (\"resize\", 1,\n                      \"subcase 2a: no or all columns with specified width\");\n         apportion2 (totalWidth, 0, colExtremes->size() - 1, MAX, MAX, NULL,\n                     colWidths, 0);\n      } else {\n         DBG_OBJ_MSGF (\"resize\", 1,\n                       \"subcase 2b: %d column(s) with specified width\",\n                       numColWidthSpecified);\n\n         // Seperate columns with specified and unspecified width, and\n         // apply apportion2() only to the latter.\n\n         int numNotSpecified = colExtremes->size() - numColWidthSpecified;\n\n         misc::SimpleVector<int> widthsNotSpecified (numNotSpecified);\n         widthsNotSpecified.setSize (numNotSpecified);\n         misc::SimpleVector<int> apportionDest (numNotSpecified);\n\n         int totalWidthNotSpecified = totalWidth, indexNotSpecified = 0;\n         for (int col = 0; col < colExtremes->size(); col++)\n            if (colWidthSpecified->get (col))\n               totalWidthNotSpecified -= colExtremes->getRef(col)->maxWidth;\n            else {\n               widthsNotSpecified.set (indexNotSpecified,\n                                       colExtremes->getRef(col)->maxWidth);\n               indexNotSpecified++;\n            }\n\n         DBG_IF_RTFL {\n            DBG_OBJ_MSGF (\"resize\", 1, \"totalWidthNotSpecified = %d\",\n                          totalWidthNotSpecified);\n\n            DBG_OBJ_MSG (\"resize\", 1, \"widthsNotSpecified:\");\n            DBG_OBJ_MSG_START ();\n\n            for (int i = 0; i < widthsNotSpecified.size (); i++)\n               DBG_OBJ_MSGF (\"resize\", 1, \"#%d: %d\",\n                             i, widthsNotSpecified.get (i));\n\n            DBG_OBJ_MSG_END ();\n         }\n\n         apportion2 (totalWidthNotSpecified, 0, numNotSpecified - 1, DATA, DATA,\n                     (void*)&widthsNotSpecified, &apportionDest, 0);\n\n         DBG_IF_RTFL {\n            DBG_OBJ_MSG (\"resize\", 1, \"apportionDest:\");\n            DBG_OBJ_MSG_START ();\n\n            for (int i = 0; i < apportionDest.size (); i++)\n               DBG_OBJ_MSGF (\"resize\", 1, \"#%d: %d\", i, apportionDest.get (i));\n\n            DBG_OBJ_MSG_END ();\n         }\n\n         DBG_OBJ_MSG (\"resize\", 1, \"finally setting column widths:\");\n         DBG_OBJ_MSG_START ();\n\n         indexNotSpecified = 0;\n         for (int col = 0; col < colExtremes->size(); col++)\n            if (colWidthSpecified->get (col)) {\n               DBG_OBJ_MSGF (\"resize\", 1, \"#%d: specified, gets maximum %d\",\n                             col, colExtremes->getRef(col)->maxWidth);\n               colWidths->set (col, colExtremes->getRef(col)->maxWidth);\n            } else {\n               DBG_OBJ_MSGF (\"resize\", 1, \"#%d: not specified, gets value %d \"\n                             \"at position %d from temporary list\",\n                             col, apportionDest.get (indexNotSpecified),\n                             indexNotSpecified);\n               colWidths->set (col, apportionDest.get (indexNotSpecified));\n               indexNotSpecified++;\n            }\n\n         DBG_OBJ_MSG_END ();\n      }\n   } else {\n      // Normal apportioning.\n      int width =\n         totalWidthSpecified ? totalWidth : misc::min (totalWidth, maxWidth);\n      DBG_OBJ_MSGF (\"resize\", 1, \"case 3: else; width = %d\", width);\n      apportion2 (width, 0, colExtremes->size() - 1, MIN, MAX, NULL, colWidths,\n                  0);\n   }\n\n   // TODO: Adapted from old inline function \"setColWidth\". But (i) is\n   // this anyway correct (col width is is not x)? And does the\n   // performance gain actually play a role?\n   for (int col = 0; col < colExtremes->size(); col++) {\n      if (colWidths->get (col) != oldColWidths->get (col))\n         redrawX = lout::misc::min (redrawX, colWidths->get (col));\n   }\n\n   DBG_IF_RTFL {\n      DBG_OBJ_SET_NUM (\"colWidths.size\", colWidths->size ());\n      for (int i = 0; i < colWidths->size (); i++)\n         DBG_OBJ_ARRSET_NUM (\"colWidths\", i, colWidths->get (i));\n   }\n\n   colWidthsUpToDateWidthColExtremes = true;\n   DBG_OBJ_SET_BOOL (\"colWidthsUpToDateWidthColExtremes\",\n                     colWidthsUpToDateWidthColExtremes);\n\n   for (int col = 0; col < numCols; col++) {\n      if (col >= oldColWidths->size () || col >= colWidths->size () ||\n          oldColWidths->get (col) != colWidths->get (col)) {\n         // Column width has changed, tell children about this.\n         for (int row = 0; row < numRows; row++) {\n            int n = row * numCols + col;\n            // TODO: Columns spanning several rows are only regarded\n            // when the first column is affected.\n            if (childDefined (n))\n               children->get(n)->cell.widget->containerSizeChanged ();\n         }\n      }\n   }\n\n   delete oldColWidths;\n\n   if (calcHeights) {\n      setCumHeight (0, 0);\n      for (int row = 0; row < numRows; row++) {\n         /**\n          * \\bug dw::Table::baseline is not filled.\n          */\n         int rowHeight = 0;\n\n         for (int col = 0; col < numCols; col++) {\n            int n = row * numCols + col;\n            if (childDefined (n)) {\n               int width = (children->get(n)->cell.colspanEff - 1)\n                  * getStyle()->hBorderSpacing;\n               for (int i = 0; i < children->get(n)->cell.colspanEff; i++)\n                  width += colWidths->get (col + i);\n\n               core::Requisition childRequisition;\n               //children->get(n)->cell.widget->setWidth (width);\n               children->get(n)->cell.widget->sizeRequest (&childRequisition);\n               childHeight = childRequisition.ascent + childRequisition.descent;\n               if (children->get(n)->cell.rowspan == 1) {\n                  rowHeight = misc::max (rowHeight, childHeight);\n               } else {\n                  rowSpanCells->increase();\n                  rowSpanCells->set(rowSpanCells->size()-1, n);\n               }\n            }\n         } // for col\n\n         setCumHeight (row + 1,\n            cumHeight->get (row) + rowHeight + getStyle()->vBorderSpacing);\n      } // for row\n\n      apportionRowSpan ();\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Table::apportionRowSpan ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"apportionRowSpan\");\n\n   int *rowHeight = NULL;\n\n   for (int c = 0; c < rowSpanCells->size(); ++c) {\n      int n = rowSpanCells->get(c);\n      int row = n / numCols;\n      int rs = children->get(n)->cell.rowspan;\n      int sumRows = cumHeight->get(row+rs) - cumHeight->get(row);\n      core::Requisition childRequisition;\n      children->get(n)->cell.widget->sizeRequest (&childRequisition);\n      int spanHeight = childRequisition.ascent + childRequisition.descent\n                       + getStyle()->vBorderSpacing;\n      if (sumRows >= spanHeight)\n         continue;\n\n      // Cell size is too small.\n      _MSG(\"Short cell %d, sumRows=%d spanHeight=%d\\n\",\n          n,sumRows,spanHeight);\n\n      // Fill height array\n      if (!rowHeight) {\n         rowHeight = new int[numRows];\n         for (int i = 0; i < numRows; i++)\n            rowHeight[i] = cumHeight->get(i+1) - cumHeight->get(i);\n      }\n#ifdef DBG\n      MSG(\" rowHeight { \");\n      for (int i = 0; i < numRows; i++)\n         MSG(\"%d \", rowHeight[i]);\n      MSG(\"}\\n\");\n#endif\n\n      // Calc new row sizes for this span.\n      int cumHnew_i = 0, cumh_i = 0, hnew_i;\n      for (int i = row; i < row + rs; ++i) {\n         hnew_i =\n            sumRows == 0 ? (int)((float)(spanHeight-cumHnew_i)/(row+rs-i)) :\n            (sumRows-cumh_i) <= 0 ? 0 :\n            (int)((float)(spanHeight-cumHnew_i)*rowHeight[i]/(sumRows-cumh_i));\n\n         _MSG(\" i=%-3d h=%d hnew_i=%d =%d*%d/%d   cumh_i=%d cumHnew_i=%d\\n\",\n             i,rowHeight[i],hnew_i,\n             spanHeight-cumHnew_i,rowHeight[i],sumRows-cumh_i,\n             cumh_i, cumHnew_i);\n\n         cumHnew_i += hnew_i;\n         cumh_i += rowHeight[i];\n         rowHeight[i] = hnew_i;\n      }\n      // Update cumHeight\n      for (int i = 0; i < numRows; ++i)\n         setCumHeight (i+1, cumHeight->get(i) + rowHeight[i]);\n   }\n   delete[] rowHeight;\n\n   DBG_OBJ_LEAVE ();\n}\n\n\n/**\n * \\brief Fills dw::Table::colExtremes in all cases.\n */\nvoid Table::forceCalcColumnExtremes ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"forceCalcColumnExtremes\");\n\n   if (numCols > 0) {\n      lout::misc::SimpleVector<int> colSpanCells (8);\n      colExtremes->setSize (numCols);\n      colWidthSpecified->setSize (numCols);\n      colWidthPercentage->setSize (numCols);\n\n      // 1. cells with colspan = 1\n      for (int col = 0; col < numCols; col++) {\n         DBG_OBJ_MSGF (\"resize\", 1, \"column %d\", col);\n         DBG_OBJ_MSG_START ();\n\n         colWidthSpecified->set (col, false);\n         colWidthPercentage->set (col, false);\n\n         colExtremes->getRef(col)->minWidth = 0;\n         colExtremes->getRef(col)->minWidthIntrinsic = 0;\n         colExtremes->getRef(col)->maxWidth = 0;\n         colExtremes->getRef(col)->maxWidthIntrinsic = 0;\n         colExtremes->getRef(col)->adjustmentWidth = 0;\n\n         for (int row = 0; row < numRows; row++) {\n            DBG_OBJ_MSGF (\"resize\", 1, \"row %d\", row);\n            DBG_OBJ_MSG_START ();\n\n            int n = row * numCols + col;\n\n            if (childDefined (n)) {\n               if (children->get(n)->cell.colspanEff == 1) {\n                  core::Extremes cellExtremes;\n                  children->get(n)->cell.widget->getExtremes (&cellExtremes);\n\n                  DBG_OBJ_MSGF (\"resize\", 1, \"child: %d / %d\",\n                                cellExtremes.minWidth, cellExtremes.maxWidth);\n\n                  colExtremes->getRef(col)->minWidthIntrinsic =\n                     misc::max (colExtremes->getRef(col)->minWidthIntrinsic,\n                                cellExtremes.minWidthIntrinsic);\n                  colExtremes->getRef(col)->maxWidthIntrinsic =\n                     misc::max (colExtremes->getRef(col)->minWidthIntrinsic,\n                                colExtremes->getRef(col)->maxWidthIntrinsic,\n                                cellExtremes.maxWidthIntrinsic);\n\n                  colExtremes->getRef(col)->minWidth =\n                     misc::max (colExtremes->getRef(col)->minWidth,\n                                cellExtremes.minWidth);\n                  colExtremes->getRef(col)->maxWidth =\n                     misc::max (colExtremes->getRef(col)->minWidth,\n                                colExtremes->getRef(col)->maxWidth,\n                                cellExtremes.maxWidth);\n\n                  colExtremes->getRef(col)->adjustmentWidth =\n                     misc::max (colExtremes->getRef(col)->adjustmentWidth,\n                                cellExtremes.adjustmentWidth);\n\n                  core::style::Length childWidth =\n                     children->get(n)->cell.widget->getStyle()->width;\n                  if (childWidth != core::style::LENGTH_AUTO) {\n                     colWidthSpecified->set (col, true);\n                     if (core::style::isPerLength (childWidth))\n                        colWidthPercentage->set (col, true);\n                  }\n\n                  DBG_OBJ_MSGF (\"resize\", 1, \"column: %d / %d (%d / %d)\",\n                                colExtremes->getRef(col)->minWidth,\n                                colExtremes->getRef(col)->maxWidth,\n                                colExtremes->getRef(col)->minWidthIntrinsic,\n                                colExtremes->getRef(col)->maxWidthIntrinsic);\n               } else {\n                  colSpanCells.increase ();\n                  colSpanCells.setLast (n);\n               }\n            }\n\n            DBG_OBJ_MSG_END ();\n         }\n\n         DBG_OBJ_MSG_END ();\n      }\n\n      // 2. cells with colspan > 1\n\n      // TODO: Is this old comment still relevant? \"If needed, here we\n      // set proportionally apportioned col maximums.\"\n\n      for (int i = 0; i < colSpanCells.size(); i++) {\n         int n = colSpanCells.get (i);\n         int col = n % numCols;\n         int cs = children->get(n)->cell.colspanEff;\n\n         core::Extremes cellExtremes;\n         children->get(n)->cell.widget->getExtremes (&cellExtremes);\n\n         calcExtremesSpanMultiCols (col, cs, &cellExtremes, MIN, MAX, NULL);\n         calcExtremesSpanMultiCols (col, cs, &cellExtremes, MIN_INTR, MAX_INTR,\n                                    NULL);\n         calcAdjustmentWidthSpanMultiCols (col, cs, &cellExtremes);\n\n         core::style::Length childWidth =\n            children->get(n)->cell.widget->getStyle()->width;\n         if (childWidth != core::style::LENGTH_AUTO) {\n            for (int j = 0; j < cs; j++)\n               colWidthSpecified->set (col + j, true);\n            if (core::style::isPerLength (childWidth))\n               for (int j = 0; j < cs; j++)\n                  colWidthPercentage->set (col + j, true);\n         }\n      }\n   }\n\n   numColWidthSpecified = 0;\n   numColWidthSpecified = 0;\n   for (int i = 0; i < colExtremes->size (); i++) {\n      if (colWidthSpecified->get (i))\n         numColWidthSpecified++;\n      if (colWidthPercentage->get (i))\n         numColWidthPercentage++;\n   }\n\n   DBG_IF_RTFL {\n      DBG_OBJ_SET_NUM (\"colExtremes.size\", colExtremes->size ());\n      for (int i = 0; i < colExtremes->size (); i++) {\n         DBG_OBJ_ARRATTRSET_NUM (\"colExtremes\", i, \"minWidth\",\n                                 colExtremes->get(i).minWidth);\n         DBG_OBJ_ARRATTRSET_NUM (\"colExtremes\", i, \"minWidthIntrinsic\",\n                                 colExtremes->get(i).minWidthIntrinsic);\n         DBG_OBJ_ARRATTRSET_NUM (\"colExtremes\", i, \"maxWidth\",\n                                 colExtremes->get(i).maxWidth);\n         DBG_OBJ_ARRATTRSET_NUM (\"colExtremes\", i, \"maxWidthIntrinsic\",\n                                 colExtremes->get(i).maxWidthIntrinsic);\n      }\n\n      DBG_OBJ_SET_NUM (\"colWidthSpecified.size\", colWidthSpecified->size ());\n      for (int i = 0; i < colWidthSpecified->size (); i++)\n         DBG_OBJ_ARRSET_BOOL (\"colWidthSpecified\", i,\n                              colWidthSpecified->get(i));\n      DBG_OBJ_SET_NUM (\"numColWidthSpecified\", numColWidthSpecified);\n\n      DBG_OBJ_SET_NUM (\"colWidthPercentage.size\", colWidthPercentage->size ());\n      for (int i = 0; i < colWidthPercentage->size (); i++)\n         DBG_OBJ_ARRSET_BOOL (\"colWidthPercentage\", i,\n                              colWidthPercentage->get(i));\n      DBG_OBJ_SET_NUM (\"numColWidthPercentage\", numColWidthPercentage);\n   }\n\n   colWidthsUpToDateWidthColExtremes = false;\n   DBG_OBJ_SET_BOOL (\"colWidthsUpToDateWidthColExtremes\",\n                     colWidthsUpToDateWidthColExtremes);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Table::calcExtremesSpanMultiCols (int col, int cs,\n                                       core::Extremes *cellExtremes,\n                                       ExtrMod minExtrMod, ExtrMod maxExtrMod,\n                                       void *extrData)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcExtremesSpanMulteCols\",\n                  \"%d, %d, ..., %s, %s, ...\",\n                  col, cs, getExtrModName (minExtrMod),\n                  getExtrModName (maxExtrMod));\n\n   int cellMin = getExtreme (cellExtremes, minExtrMod);\n   int cellMax = getExtreme (cellExtremes, maxExtrMod);\n\n   int minSumCols = 0, maxSumCols = 0;\n\n   for (int j = 0; j < cs; j++) {\n      minSumCols += getColExtreme (col + j, minExtrMod, extrData);\n      maxSumCols += getColExtreme (col + j, maxExtrMod, extrData);\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"cs = %d, cell: %d / %d, sum: %d / %d\\n\",\n                 cs, cellMin, cellMax, minSumCols, maxSumCols);\n\n   bool changeMin = cellMin > minSumCols;\n   bool changeMax = cellMax > maxSumCols;\n   if (changeMin || changeMax) {\n      // TODO This differs from the documentation? Should work, anyway.\n      misc::SimpleVector<int> newMin, newMax;\n      if (changeMin)\n         apportion2 (cellMin, col, col + cs - 1, MIN, MAX, NULL, &newMin, 0);\n      if (changeMax)\n         apportion2 (cellMax, col, col + cs - 1, MIN, MAX, NULL, &newMax, 0);\n\n      for (int j = 0; j < cs; j++) {\n         if (changeMin)\n            setColExtreme (col + j, minExtrMod, extrData, newMin.get (j));\n         if (changeMax)\n            setColExtreme (col + j, maxExtrMod, extrData, newMax.get (j));\n\n         // For cases where min and max are somewhat confused:\n         setColExtreme (col + j, maxExtrMod, extrData,\n                        misc::max (getColExtreme (col + j, minExtrMod,\n                                                  extrData),\n                                   getColExtreme (col + j, maxExtrMod,\n                                                  extrData)));\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Table::calcAdjustmentWidthSpanMultiCols (int col, int cs,\n                                              core::Extremes *cellExtremes)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcAdjustmentWidthSpanMultiCols\",\n                  \"%d, %d, ...\", col, cs);\n\n   int sumAdjustmentWidth = 0;\n   for (int j = 0; j < cs; j++)\n      sumAdjustmentWidth +=  colExtremes->getRef(col + j)->adjustmentWidth;\n   \n   if (cellExtremes->adjustmentWidth > sumAdjustmentWidth) {\n      misc::SimpleVector<int> newAdjustmentWidth;\n      apportion2 (cellExtremes->adjustmentWidth, col, col + cs - 1, MIN, MAX,\n                  NULL, &newAdjustmentWidth, 0);\n      for (int j = 0; j < cs; j++)\n         colExtremes->getRef(col + j)->adjustmentWidth =\n            newAdjustmentWidth.get (j);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * \\brief Actual apportionment function.\n */\nvoid Table::apportion2 (int totalWidth, int firstCol, int lastCol,\n                        ExtrMod minExtrMod, ExtrMod maxExtrMod, void *extrData,\n                        misc::SimpleVector<int> *dest, int destOffset)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"apportion2\", \"%d, %d, %d, %s, %s, ..., %d\",\n                  totalWidth, firstCol, lastCol, getExtrModName (minExtrMod),\n                  getExtrModName (maxExtrMod), destOffset);\n\n   if (lastCol >= firstCol) {\n      dest->setSize (destOffset + lastCol - firstCol + 1, 0);\n\n      int totalMin = 0, totalMax = 0;\n      for (int col = firstCol; col <= lastCol; col++) {\n         totalMin += getColExtreme (col, minExtrMod, extrData);\n         totalMax += getColExtreme (col, maxExtrMod, extrData);\n      }\n\n      DBG_OBJ_MSGF (\"resize\", 1,\n                    \"totalWidth = %d, totalMin = %d, totalMax = %d\",\n                    totalWidth, totalMin, totalMax);\n\n      // The actual calculation is rather simple, the ith value is:\n      //\n      //\n      //                     (max[i] - min[i]) * (totalMax - totalMin)\n      // width[i] = min[i] + -----------------------------------------\n      //                              (totalWidth - totalMin)\n      //\n      // (Regard \"total\" as \"sum\".) With the following general\n      // definitions (for both the list and sums):\n      //\n      //    diffExtr = max - min\n      //    diffWidth = width - min\n      //\n      // it is simplified to:\n      //\n      //                   diffExtr[i] * totalDiffWidth\n      //    diffWidth[i] = ----------------------------\n      //                           totalDiffExtr\n      //\n      // Of course, if totalDiffExtr is 0, this is not defined;\n      // instead, we apportion according to the minima:\n      //\n      //               min[i] * totalWidth\n      //    width[i] = -------------------\n      //                    totalMin\n      //\n      // Since min[i] <= max[i] for all i, totalMin == totalMax\n      // implies that min[i] == max[i] for all i.\n      //\n      // Third, it totalMin == 0 (which also implies min[i] = max[i] = 0),\n      // the result is\n      //\n      //    width[i] = totalWidth / n\n\n      int totalDiffExtr = totalMax - totalMin;\n      if (totalDiffExtr != 0) {\n         // Normal case. The algorithm described in\n         // \"rounding-errors.doc\" is used, with:\n         //\n         //    x[i] = diffExtr[i]\n         //    y[i] = diffWidth[i]\n         //    a = totalDiffWidth\n         //    b = totalDiffExtr\n\n         DBG_OBJ_MSG (\"resize\", 1, \"normal case\");\n\n         int totalDiffWidth = totalWidth - totalMin;\n         int cumDiffExtr = 0, cumDiffWidth = 0;\n\n         for (int col = firstCol; col <= lastCol; col++) {\n            int min = getColExtreme (col, minExtrMod, extrData);\n            int max = getColExtreme (col, maxExtrMod, extrData);\n            int diffExtr = max - min;\n\n            cumDiffExtr += diffExtr;\n            int diffWidth =\n               (cumDiffExtr * totalDiffWidth) / totalDiffExtr - cumDiffWidth;\n            cumDiffWidth += diffWidth;\n\n            dest->set (destOffset - firstCol + col, diffWidth + min);\n         }\n      } else if (totalMin != 0) {\n         // Special case. Again, same algorithm, with\n         //\n         //    x[i] = min[i]\n         //    y[i] = width[i]\n         //    a = totalWidth\n         //    b = totalMin\n\n         DBG_OBJ_MSG (\"resize\", 1, \"special case 1\");\n\n         int cumMin = 0, cumWidth = 0;\n         for (int col = firstCol; col <= lastCol; col++) {\n            int min = getColExtreme (col, minExtrMod, extrData);\n            cumMin += min;\n            int width = (cumMin * totalWidth) / totalMin - cumWidth;\n            cumWidth += width;\n\n            dest->set (destOffset - firstCol + col, width);\n         }\n      } else { // if (totalMin == 0)\n         // Last special case. Ssame algorithm, with\n         //\n         //    x[i] = 1 (so cumX = i = col - firstCol + 1)\n         //    y[i] = width[i]\n         //    a = totalWidth\n         //    b = n = lastCol - firstCol + 1\n\n         DBG_OBJ_MSG (\"resize\", 1, \"special case 2\");\n\n         int cumWidth = 0, n = (lastCol - firstCol + 1);\n         for (int col = firstCol; col <= lastCol; col++) {\n            int i = (col - firstCol + 1);\n            int width = (i * totalWidth) / n - cumWidth;\n            cumWidth += width;\n\n            dest->set (destOffset - firstCol + col, width);\n         }\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/table.hh",
    "content": "#ifndef __DW_TABLE_HH__\n#define __DW_TABLE_HH__\n\n#include \"oofawarewidget.hh\"\n#include \"alignedtablecell.hh\"\n#include \"../lout/misc.hh\"\n\nnamespace dw {\n\n/**\n * \\brief A Widget for rendering tables.\n *\n * <div style=\"border: 2px solid #ff0000; margin-top: 0.5em;\n * margin-bottom: 0.5em; padding: 0.5em 1em;\n * background-color: #ffefe0\"><b>Warning:</b> Some parts of this\n * description are outdated since \\ref dw-grows.</div>\n *\n * <h3>Introduction</h3>\n *\n * The dw::Table widget is used to render HTML tables.\n *\n * Each cell is itself a separate widget. Any widget may be used, however, in\n * dillo, only instances of dw::Textblock and dw::TableCell are used as\n * children of dw::Table.\n *\n *\n * <h3>Sizes</h3>\n *\n * <h4>General</h4>\n *\n * The following diagram shows the dependencies between the different\n * functions, which are related to size calculation. Click on the boxes\n * for more information.\n *\n * \\dot\n * digraph G {\n *    node [shape=record, fontname=Helvetica, fontsize=10, color=\"#c0c0c0\"];\n *    edge [arrowhead=\"open\", arrowtail=\"none\", labelfontname=Helvetica,\n *          labelfontsize=10, color=\"#404040\", labelfontcolor=\"#000080\",\n *          fontname=Helvetica, fontsize=10];\n *    fontname=Helvetica; fontsize=10;\n *\n *    sizeRequestImpl [color=\"#0000ff\", URL=\"\\ref dw::Table::sizeRequestImpl\"];\n *    sizeAllocateImpl [color=\"#0000ff\",\n *                      URL=\"\\ref dw::Table::sizeAllocateImpl\"];\n *    getExtremes [color=\"#0000ff\", URL=\"\\ref dw::core::Widget::getExtremes\"];\n *    getExtremesImpl [color=\"#0000ff\", URL=\"\\ref dw::Table::getExtremesImpl\"];\n *\n *    calcCellSizes [label=\"calcCellSizes (calcHeights = true)\",\n *                   URL=\"\\ref dw::Table::calcCellSizes\"];\n *    forceCalcCellSizes [label=\"forceCalcCellSizes (calcHeights = true)\",\n *                        URL=\"\\ref dw::Table::forceCalcCellSizes\"];\n *    actuallyCalcCellSizes[label=\"actuallyCalcCellSizes (calcHeights = true)\",\n *                          URL=\"\\ref dw::Table::actuallyCalcCellSizes\"];\n *    forceCalcColumnExtremes[URL=\"\\ref dw::Table::forceCalcColumnExtremes\"];\n *\n *    sizeRequestImpl -> forceCalcCellSizes [label=\"[B]\"];\n *    sizeAllocateImpl -> calcCellSizes [label=\"[A]\"];\n *    getExtremesImpl -> forceCalcColumnExtremes [label=\"[B]\"];\n *\n *    forceCalcCellSizes -> actuallyCalcCellSizes;\n *    actuallyCalcCellSizes-> getExtremes;\n *    getExtremes -> getExtremesImpl [style=\"dashed\", label=\"[C]\"];\n *\n *    calcCellSizes -> forceCalcCellSizes [style=\"dashed\", label=\"[C]\"];\n * }\n * \\enddot\n *\n * [A] In this case, the new calculation is \\em not forced, but only\n * done, when necessary.\n *\n * [B] In this case, the new calculation is allways necessary, since [C]\n * is the case.\n *\n * [C] Whether this function is called, depends on NEEDS_RESIZE /\n * RESIZE_QUEUED / EXTREMES_CHANGED / EXTREMES_QUEUED.\n *\n * **TODO:**\n *\n * - Are <tt>*[cC]alcCellSizes (calcHeights = *false*)</tt> not\n *   necessary anymore?\n * - Calculating available sizes (Table::getAvailWidthOfChild) should\n *   be documented in this diagram, too.\n *\n * <h4>Apportionment</h4>\n *\n * \\sa\\ref rounding-errors\n *\n * Given two array \\f$e_{i,\\min}\\f$ and \\f$e_{i,\\max}\\f$, which\n * represent the column minima and maxima, and a total width \\f$W\\f$, \\em\n * apportionment means to calculate column widths \\f$w_{i}\\f$, with\n *\n * \\f[e_{i,\\min} \\le w_{i} \\le e_{i,\\max}\\f]\n *\n * and\n *\n * \\f[\\sum w_{i} = W\\f]\n *\n * There are different algorithms for apportionment, a simple one is\n * recommended in the HTML 4.0.1 specification\n * (http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.5.2.2):\n *\n * \\f[w_{i} = e_{i,\\min} +\n *    {e_{i,\\max} - e_{i,\\min}\\over\\sum e_{i,\\max} - \\sum e_{i,\\min}}\n *    (W - \\sum e_{i,\\min})\\f]\n *\n * This one is used currently, but another one will be used soon, which is\n * described below. The rest of this chapter is independent of the exact\n * apportionment algorithm.\n *\n * When referring to the apportionment function, we will call it\n * \\f$a_i (W, (e_{i,\\min}), (e_{i,\\min}))\\f$ and write\n * something like this:\n *\n * \\f[w_{i} = a_i (W, (e_{i,\\min}), (e_{i,\\max})) \\f]\n *\n * It is implemented by dw::Table::apportion.\n *\n * <h4>Column Extremes</h4>\n *\n * \\sa\\ref rounding-errors\n *\n * The sizes, which all other sizes depend on, are column extremes, which\n * define, how wide a column may be at min and at max. They are\n * calculated in the following way:\n *\n * <ol>\n * <li> First, only cells with colspan = 1 are regarded:\n *      \\f[ e_{\\hbox{base},i,\\min} = \\max \\{ e_{\\hbox{cell},i,j,\\min} \\} \\f]\n *      \\f[ e_{\\hbox{base},i,\\max} = \\max \\{ e_{\\hbox{cell},i,j,\\max} \\} \\f]\n *      only for cells \\f$(i, j)\\f$ with colspan = 1.\n *\n * <li> Then,\n *      \\f$e_{\\hbox{span},i,\\min}\\f$ (but not \\f$e_{\\hbox{span},i,\\max}\\f$)\n *      are calculated from cells with colspan > 1. (In the following formulas,\n *      the cell at \\f$(i_1, j)\\f$ always span from \\f$i_1\\f$ to \\f$i_2\\f$.)\n *      If the minimal width of the column exceeds the sum of the column minima\n *      calculated in the last step:\n *      \\f[e_{\\hbox{cell},i_1,j,\\min} >\n *         \\sum_{i=i_1}^{i=i_2} e_{\\hbox{base},i,\\min}\\f]\n *      then the minimal width of this cell is apportioned to the columns:\n *\n *      <ul>\n *      <li> If the minimal width of this cell also exceeds the sum of the\n *           column maxima:\n *        \\f[e_{\\hbox{cell},i_1,j,\\min} >\n *           \\sum_{i=i_1}^{i=i_2} e_{\\hbox{base},i,\\max}\\f]\n *           then \\f$e_{\\hbox{cell},i_1,j,\\min}\\f$ is apportioned in a simple\n *           way:\n *        \\f[e_{\\hbox{span},i,j,\\min} =\n *              e_{\\hbox{base},i,\\max}\n *                 {e_{\\hbox{span},i,j,\\min} \\over\n *                  \\sum_{i=i_1}^{i=i_2} e_{\\hbox{base},i,\\max}}\\f]\n *      <li> Otherwise, the apportionment function is used:\n *        \\f[e_{\\hbox{span},i,j,\\min} =\n *           a_i (e_{\\hbox{cell},i_1,j,\\min},\n *                (e_{\\hbox{cell},i_1,j,\\min} \\ldots\n *                    e_{\\hbox{cell},i_2,j,\\min}),\n *                (e_{\\hbox{cell},i_1,j,\\max} \\ldots\n *                    e_{\\hbox{cell},i_2,j,\\max}))\\f]\n *      </ul>\n *\n *      After this, \\f$e_{\\hbox{span},i,\\min}\\f$ is then the maximum of all\n *      \\f$e_{\\hbox{span},i,j,\\min}\\f$.\n *\n * <li> Finally, the maximum of both is used.\n *      \\f[ e_{i,\\min} =\n *         \\max \\{ e_{\\hbox{base},i,\\min}, e_{\\hbox{span},i,\\min} \\} \\f]\n *      \\f[ e_{i,\\max} =\n *         \\max \\{ e_{\\hbox{base},i,\\max}, e_{i,\\min} \\} \\f]\n *      For the maxima, there is no \\f$e_{\\hbox{span},i,\\max}\\f$, but it has to\n *      be assured, that the maximum is always greater than or equal to the\n *      minimum.\n *\n * </ol>\n *\n * Generally, if absolute widths are specified, they are, instead of the\n * results of dw::core::Widget::getExtremes, taken for the minimal and\n * maximal width of a cell (minus the box difference, i.e. the difference\n * between content size and widget size). If the content width\n * specification is smaller than the minimal content width of the widget\n * (determined by dw::core::Widget::getExtremes), the latter is used\n * instead.\n *\n * If percentage widths are specified, they are also collected, as column\n * maxima. A similar method as for the extremes is used, for cells with\n * colspan > 1:\n *\n * \\f[w_{\\hbox{span},i,j,\\%} =\n *    a_i (w_{\\hbox{cell},i_1,j,\\%},\n *      (e_{\\hbox{cell},i_1,j,\\min} \\ldots e_{\\hbox{cell},i_2,j,\\min}),\n *      (e_{\\hbox{cell},i_1,j,\\max} \\ldots e_{\\hbox{cell},i_2,j,\\max}))\\f]\n *\n * <h4>Cell Sizes</h4>\n *\n * <h5>Determining the Width of the Table</h5>\n *\n * The total width is\n *\n * <ul>\n * <li> the specified absolute width of the table, when given, or\n * <li> the available width (set by dw::Table::setWidth [TODO outdated]) times\n *      the specified percentage width of t(at max 100%), if the latter is\n *      given, or\n * <li> otherwise the available width.\n * </ul>\n *\n * In any case, it is corrected, if it is less than the minimal width\n * (but not if it is greater than the maximal width).\n *\n * \\bug The parentheses is not fully clear, look at the old code.\n *\n * Details on differences because of styles are omitted. Below, this\n * total width is called \\f$W\\f$.\n *\n * <h5>Evaluating percentages</h5>\n *\n * The following algorithms are used to solve collisions between\n * different size specifications (absolute and percentage). Generally,\n * inherent sizes and specified absolute sizes are preferred.\n *\n * <ol>\n * <li> First, calculate the sum of the minimal widths, for columns, where\n *      no percentage width has been specified. The difference to the total\n *      width is at max available to the columns with percentage width\n *      specifications:\n *      \\f[W_{\\hbox{columns}_\\%,\\hbox{available}} = W - \\sum e_{i,\\min}\\f]\n *      with only those columns \\f$i\\f$ with no percentage width specification.\n *\n * <li> Then, calculate the sum of the widths, which the columns with\n *      percentage width specification would allocate, when fully adhering to\n *      them:\n *      \\f[W_{\\hbox{columns}_\\%,\\hbox{best}} = W \\sum w_{i,\\%}\\f]\n *      with only those columns \\f$i\\f$ with a percentage width specification.\n *\n * <li> Two cases are distinguished:\n *\n *      <ul>\n *      <li> \\f$W_{\\hbox{columns}_\\%,\\hbox{available}} \\ge\n *             W_{\\hbox{columns}_\\%,\\hbox{best}}\\f$: In this case, the\n *           percentage widths can be used without any modification, by\n *           setting the extremes:\n *           \\f[e_{i,\\min} = e_{i,\\max} = W w_{i,\\%}\\f]\n *           for only those columns \\f$i\\f$ with a percentage width\n *           specification.\n *\n *      <li> \\f$W_{\\hbox{columns}_\\%,\\hbox{available}} <\n *             W_{\\hbox{columns}_\\%,\\hbox{best}}\\f$: In this case, the widths\n *           for these columns must be cut down:\n *           \\f[e_{i,\\min} = e_{i,\\max} =\n *              w_{i,\\%}\n *              {W_{\\hbox{columns}_\\%,\\hbox{available}} \\over\n *               w_{\\hbox{total},\\%}}\\f]\n *           with\n *           \\f[w_{\\hbox{total},\\%} = \\sum w_{i,\\%}\\f]\n *           in both cases for only those columns \\f$i\\f$ with a percentage\n *           width specification.\n *      </ul>\n * </ol>\n *\n * (\\f$e_{i,\\min}\\f$ and \\f$e_{i,\\max}\\f$ are set \\em temporarily here,\n * the notation should be a bit clearer.)\n *\n *\n * <h5>Column Widths</h5>\n *\n * The column widths are now simply calculated by applying the\n * apportionment function.\n *\n *\n * <h5>Row Heights</h5>\n *\n * ...\n *\n * <h3>Alternative Apportionment Algorithm</h3>\n *\n * The algorithm described here tends to result in more homogeneous column\n * widths.\n *\n * The following rule leads to well-defined \\f$w_{i}\\f$: All columns\n * \\f$i\\f$ have have the same width \\f$w\\f$, except:\n * <ul>\n * <li> \\f$w < e_{i,\\min}\\f$, or\n * <li> \\f$w > e_{i,\\max}\\f$.\n * </ul>\n *\n * Furthermore, \\f$w\\f$ is\n * <ul>\n * <li> less than all \\f$e_{i,\\min}\\f$ of columns not having \\f$w\\f$ as\n *      width, and\n * <li> greater than all \\f$e_{i,\\min}\\f$ of columns not having \\f$w\\f$ as\n *      width.\n * </ul>\n *\n * Of course, \\f$\\sum w_{i} = W\\f$ must be the case.\n *\n * Based on an initial value \\f$w = {W\\over n}\\f$, \\f$w\\f$ can iteratively\n * adjusted, based on these rules.\n *\n *\n * <h3>Borders, Paddings, Spacing</h3>\n *\n * Currently, DwTable supports only the separated borders model (see CSS\n * specification). Borders, paddings, spacing is done by creating\n * dw::core::style::Style structures with values equivalent to following CSS:\n *\n * <pre>\n * TABLE {\n *   border:           outset \\em table-border;\n *   border-collapse:  separate;\n *   border-spacing:   \\em table-cellspacing;\n *   background-color: \\em table-bgcolor;\n * }\n *\n * TD TH {\n *   border:           inset \\em table-border;\n *   padding:          \\em table-cellspacing;\n *   background-color: \\em td/th-bgcolor;\n * }\n * </pre>\n *\n * Here, \\em foo-bar refers to the attribute \\em bar of the tag \\em foo foo.\n * Look at the HTML parser for more details.\n */\nclass Table: public oof::OOFAwareWidget\n{\nprivate:\n   struct Child\n   {\n      enum {\n         CELL,       // cell starts here\n         SPAN_SPACE  // part of a spanning cell\n      } type;\n      union {\n         struct {\n            core::Widget *widget;\n            int colspanOrig, colspanEff, rowspan;\n         } cell;\n         struct {\n            int startCol, startRow;  // where the cell starts\n         } spanSpace;\n      };\n   };\n\n   class TableIterator: public OOFAwareWidgetIterator\n   {\n   protected:\n      int numContentsInFlow ();\n      void getContentInFlow (int index, core::Content *content);\n\n   public:\n      TableIterator (Table *table, core::Content::Type mask, bool atEnd);\n\n      lout::object::Object *clone();\n\n      void highlight (int start, int end, core::HighlightLayer layer);\n      void unhighlight (int direction, core::HighlightLayer layer);\n      void getAllocation (int start, int end, core::Allocation *allocation);\n   };\n\n   friend class TableIterator;\n\n   static bool adjustTableMinWidth;\n\n   bool limitTextWidth, rowClosed;\n\n   int numRows, numCols, curRow, curCol;\n   lout::misc::SimpleVector<Child*> *children;\n\n   int redrawX, redrawY;\n\n   /**\n    * \\brief The extremes of all columns.\n    */\n   lout::misc::SimpleVector<core::Extremes> *colExtremes;\n\n   /**\n    * \\brief Wether the column itself (in the future?) or at least one\n    *    cell in this column or spanning over this column has CSS\n    *    'width' specified.\n    *\n    * Filled by forceCalcColumnExtremes(), since it is needed to\n    * calculate the column widths.\n    */\n   lout::misc::SimpleVector<bool> *colWidthSpecified;\n   int numColWidthSpecified;\n\n   /**\n    * \\brief Wether the column itself (in the future?) or at least one\n    *    cell in this column or spanning over this column has CSS\n    *    'width' specified *as percentage value*.\n    *\n    * Filled by forceCalcColumnExtremes(), since it is needed to\n    * calculate the column widths.\n    */\n   lout::misc::SimpleVector<bool> *colWidthPercentage;\n   int numColWidthPercentage;\n\n   /**\n    * \\brief The widths of all columns.\n    */\n   lout::misc::SimpleVector<int> *colWidths;\n\n   /**\n    * Row cumulative height array: cumHeight->size() is numRows + 1,\n    * cumHeight->get(0) is 0, cumHeight->get(numRows) is the total table\n    * height.\n    */\n   lout::misc::SimpleVector<int> *cumHeight;\n   /**\n    * If a Cell has rowspan > 1, it goes into this array\n    */\n   lout::misc::SimpleVector<int> *rowSpanCells;\n   lout::misc::SimpleVector<int> *baseline;\n\n   lout::misc::SimpleVector<core::style::Style*> *rowStyle;\n\n   bool colWidthsUpToDateWidthColExtremes;\n\n   enum ExtrMod { MIN, MIN_INTR, MIN_MIN, MAX_MIN, MAX, MAX_INTR, DATA };\n\n   const char *getExtrModName (ExtrMod mod);\n   int getExtreme (core::Extremes *extremes, ExtrMod mod);\n   void setExtreme (core::Extremes *extremes, ExtrMod mod, int value);\n   int getColExtreme (int col, ExtrMod mod, void *data);\n   inline void setColExtreme (int col, ExtrMod mod, void *data, int value);\n\n   inline bool childDefined(int n)\n   {\n      return n < children->size() && children->get(n) != NULL &&\n         children->get(n)->type != Child::SPAN_SPACE;\n   }\n\n   int calcAvailWidthForDescendant (Widget *child);\n\n   void reallocChildren (int newNumCols, int newNumRows);\n\n   void calcCellSizes (bool calcHeights);\n   void forceCalcCellSizes (bool calcHeights);\n   void actuallyCalcCellSizes (bool calcHeights);\n   void apportionRowSpan ();\n\n   void forceCalcColumnExtremes ();\n   void calcExtremesSpanMultiCols (int col, int cs,\n                                   core::Extremes *cellExtremes,\n                                   ExtrMod minExtrMod, ExtrMod maxExtrMod,\n                                   void *extrData);\n   void calcAdjustmentWidthSpanMultiCols (int col, int cs,\n                                          core::Extremes *cellExtremes);\n\n   void apportion2 (int totalWidth, int firstCol, int lastCol,\n                    ExtrMod minExtrMod, ExtrMod maxExtrMod, void *extrData,\n                    lout::misc::SimpleVector<int> *dest, int destOffset);\n\n   void setCumHeight (int row, int value)\n   {\n      if (value != cumHeight->get (row)) {\n         redrawY = lout::misc::min ( redrawY, value );\n         cumHeight->set (row, value);\n      }\n   }\n\nprotected:\n   void sizeRequestSimpl (core::Requisition *requisition);\n   void getExtremesSimpl (core::Extremes *extremes);\n   void sizeAllocateImpl (core::Allocation *allocation);\n   void resizeDrawImpl ();\n\n   bool getAdjustMinWidth () { return Table::adjustTableMinWidth; }\n\n   int getAvailWidthOfChild (Widget *child, bool forceValue);\n\n   void containerSizeChangedForChildren ();\n   bool affectsSizeChangeContainerChild (Widget *child);\n   bool usesAvailWidth ();\n\n   bool isBlockLevel ();\n\n   void drawLevel (core::View *view, core::Rectangle *area, int level,\n                   core::DrawingContext *context);\n\n   Widget *getWidgetAtPointLevel (int x, int y, int level,\n                                  core::GettingWidgetAtPointContext *context);\n\n   //bool buttonPressImpl (core::EventButton *event);\n   //bool buttonReleaseImpl (core::EventButton *event);\n   //bool motionNotifyImpl (core::EventMotion *event);\n\n   void removeChild (Widget *child);\n\npublic:\n   static int CLASS_ID;\n\n   inline static void setAdjustTableMinWidth (bool adjustTableMinWidth)\n   { Table::adjustTableMinWidth = adjustTableMinWidth; }\n\n   inline static bool getAdjustTableMinWidth ()\n   { return Table::adjustTableMinWidth; }\n\n   Table(bool limitTextWidth);\n   ~Table();\n\n   int applyPerWidth (int containerWidth, core::style::Length perWidth);\n   int applyPerHeight (int containerHeight, core::style::Length perHeight);\n\n   core::Iterator *iterator (core::Content::Type mask, bool atEnd);\n\n   void addCell (Widget *widget, int colspan, int rowspan);\n   void addRow (core::style::Style *style);\n   AlignedTableCell *getCellRef ();\n};\n\n} // namespace dw\n\n#endif // __DW_TABLE_HH__\n"
  },
  {
    "path": "dw/table_iterator.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007, 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * (This file was originally part of textblock.cc.)\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"table.hh\"\n\nusing namespace lout;\n\nnamespace dw {\n\nTable::TableIterator::TableIterator (Table *table,\n                                     core::Content::Type mask, bool atEnd):\n   OOFAwareWidgetIterator (table, mask, atEnd, table->children->size ())\n{\n}\n\nobject::Object *Table::TableIterator::clone()\n{\n   TableIterator *tIt =\n      new TableIterator ((Table*)getWidget(), getMask(), false);\n   cloneValues (tIt);\n   return tIt;\n}\n\n\nvoid Table::TableIterator::highlight (int start, int end,\n                                      core::HighlightLayer layer)\n{\n   if (inFlow ()) {\n      /** todo Needs this an implementation? */\n   } else\n      highlightOOF (start, end, layer);\n}\n\nvoid Table::TableIterator::unhighlight (int direction,\n                                        core::HighlightLayer layer)\n{\n   if (inFlow ()) {\n      // ???\n   } else\n      unhighlightOOF (direction, layer);\n}\n\nvoid Table::TableIterator::getAllocation (int start, int end,\n                                                  core::Allocation *allocation)\n{\n   if (inFlow ()) {\n      /** \\bug Not implemented. */\n   } else\n      getAllocationOOF (start, end, allocation);\n}\n\nint Table::TableIterator::numContentsInFlow ()\n{\n   return ((Table*)getWidget())->children->size ();\n}\n\nvoid Table::TableIterator::getContentInFlow (int index,\n                                             core::Content *content)\n{\n   Table *table = (Table*)getWidget();\n\n   if (table->children->get(index) != NULL &&\n       table->children->get(index)->type == Child::CELL) {\n      content->type = core::Content::WIDGET_IN_FLOW;\n      content->widget = table->children->get(index)->cell.widget;\n   } else\n      content->type = core::Content::INVALID;       \n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/tablecell.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"tablecell.hh\"\n#include \"table.hh\"\n\nusing namespace lout;\n\nnamespace dw {\n\n/**\n * \\brief Provided some common implementations of virtual widget\n *    methods.\n *\n * Once I understand how diamond inheritance works, a class TableCell\n * will be provided, from which SimpleTableCell and AlignedTableCell\n * will inherit, additionaly (in a multiple way).\n */\nnamespace tablecell {\n\nbool getAdjustMinWidth ()\n{\n   return Table::getAdjustTableMinWidth ();\n}\n\nbool isBlockLevel ()\n{\n   return false;\n}\n\nint correctAvailWidthOfChild (core::Widget *widget, core::Widget *child,\n                              int width, bool forceValue)\n{\n   DBG_OBJ_ENTER_O (\"resize\", 0, widget, \"tablecell::correctAvailWidthOfChild\",\n                    \"%p, %d, %s\", child, width, forceValue ? \"true\" : \"false\");\n\n   // Make sure that this width does not exceed the width of the table\n   // cell (minus margin/border/padding).\n\n   if (width != -1) {\n      int thisWidth = widget->getAvailWidth (forceValue);\n      DBG_OBJ_MSGF_O (\"resize\", 1, widget, \"thisWidth = %d\", thisWidth);\n      if (thisWidth != -1)\n         width =\n            lout::misc::max (lout::misc::min (width,\n                                              thisWidth\n                                              - widget->boxDiffWidth ()),\n                             0);\n   }\n\n   DBG_OBJ_MSGF_O (\"resize\", 1, widget, \"=> %d\", width);\n   DBG_OBJ_LEAVE_O (widget);\n   return width;\n}\n\nint correctAvailHeightOfChild (core::Widget *widget, core::Widget *child,\n                               int height, bool forceValue)\n{\n   // Something to do?\n   return height;\n}\n\nvoid correctCorrectedRequisitionOfChild (core::Widget *widget,\n                                         core::Widget *child,\n                                         core::Requisition *requisition,\n                                         void (*splitHeightFun) (int, int*,\n                                                                 int*),\n                                         bool allowDecreaseWidth,\n                                         bool allowDecreaseHeight)\n{\n   DBG_OBJ_ENTER_O (\"resize\", 0, widget, \"tablecell::correctRequisitionOfChild\",\n                    \"%p, %d * (%d + %d), ..., %s, %s\",\n                    child, requisition->width, requisition->ascent,\n                    requisition->descent, misc::boolToStr (allowDecreaseWidth),\n                    misc::boolToStr (allowDecreaseHeight));\n\n   // Make sure that this width does not exceed the width of the table\n   // cell (minus margin/border/padding).\n\n   int thisWidth = widget->getAvailWidth (true);\n   DBG_OBJ_MSGF_O (\"resize\", 1, widget, \"thisWidth = %d\", thisWidth);\n   int newWidth =\n      lout::misc::max (lout::misc::min (requisition->width,\n                                        thisWidth - widget->boxDiffWidth ()),\n                       0);\n   requisition->width = allowDecreaseWidth ?\n      newWidth : misc::max (requisition->width, newWidth);\n\n   DBG_OBJ_LEAVE_O (widget);\n}\n\nvoid correctCorrectedExtremesOfChild (core::Widget *widget, core::Widget *child,\n                                      core::Extremes *extremes,\n                                      bool useAdjustmentWidth)\n{\n   // Something to do?\n}\n\nint applyPerWidth (core::Widget *widget, int containerWidth,\n                   core::style::Length perWidth)\n{\n   return core::style::multiplyWithPerLength (containerWidth, perWidth);\n}\n\nint applyPerHeight (core::Widget *widget, int containerHeight,\n                    core::style::Length perHeight)\n{\n   return core::style::multiplyWithPerLength (containerHeight, perHeight);\n}\n\n} // namespace dw\n\n} // namespace dw\n"
  },
  {
    "path": "dw/tablecell.hh",
    "content": "#ifndef __DW_TABLECELL_HH__\n#define __DW_TABLECELL_HH__\n\n#include \"core.hh\"\n\nnamespace dw {\n\nnamespace tablecell {\n\ninline bool usesMaxGeneratorWidth () { return true; }\n\nbool getAdjustMinWidth ();\nbool isBlockLevel ();\n\nint correctAvailWidthOfChild (core::Widget *widget, core::Widget *child,\n                              int width, bool forceValue);\nint correctAvailHeightOfChild (core::Widget *widget, core::Widget *child,\n                               int height, bool forceValue);\n\nvoid correctCorrectedRequisitionOfChild (core::Widget *widget,\n                                         core::Widget *child,\n                                         core::Requisition *requisition,\n                                         void (*splitHeightFun) (int, int*,\n                                                                 int*),\n                                         bool allowDecreaseWidth,\n                                         bool allowDecreaseHeight);\nvoid correctCorrectedExtremesOfChild (core::Widget *widget, core::Widget *child,\n                                      core::Extremes *extremes,\n                                      bool useAdjustmentWidth);\n\nint applyPerWidth (core::Widget *widget, int containerWidth,\n                   core::style::Length perWidth);\nint applyPerHeight (core::Widget *widget, int containerHeight,\n                    core::style::Length perHeight);\n\ninline bool adjustExtraSpaceWhenCorrectingRequisitionByOOF () { return false; }\n\n} // namespace dw\n\n} // namespace dw\n\n#endif // __DW_TABLECELL_HH__\n"
  },
  {
    "path": "dw/textblock.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007, 2012-2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"textblock.hh\"\n#include \"../lout/msg.h\"\n#include \"../lout/misc.hh\"\n#include \"../lout/unicode.hh\"\n#include \"../lout/debug.hh\"\n\n#include <stdio.h>\n#include <math.h> // remove again?\n#include <limits.h>\n\n/*\n * Local variables\n */\n/* The tooltip under mouse pointer in current textblock. No ref. hold.\n * (having one per view looks not worth the extra clutter). */\nstatic dw::core::style::Tooltip *hoverTooltip = NULL;\n\n\nusing namespace lout;\nusing namespace lout::misc;\nusing namespace lout::unicode;\n\nnamespace dw {\n\nint Textblock::CLASS_ID = -1;\n\nTextblock::WordImgRenderer::WordImgRenderer (Textblock *textblock,\n                                             int wordNo)\n{\n   //printf (\"new WordImgRenderer %p\\n\", this);\n\n   this->textblock = textblock;\n   this->wordNo = wordNo;\n   dataSet = false;\n}\n\nTextblock::WordImgRenderer::~WordImgRenderer ()\n{\n   //printf (\"delete WordImgRenderer %p\\n\", this);\n}\n\nvoid Textblock::WordImgRenderer::setData (int xWordWidget, int lineNo)\n{\n   dataSet = true;\n   this->xWordWidget = xWordWidget;\n   this->lineNo = lineNo;\n}\n\nbool Textblock::WordImgRenderer::readyToDraw ()\n{\n   //print ();\n   //printf (\"\\n\");\n\n   return dataSet && textblock->wasAllocated ()\n      && wordNo < textblock->words->size()\n      && lineNo < textblock->lines->size();\n}\n\nvoid Textblock::WordImgRenderer::getBgArea (int *x, int *y, int *width,\n                                            int *height)\n{\n   // TODO Subtract margin and border (padding box)?\n   Line *line = textblock->lines->getRef (lineNo);\n   *x = textblock->allocation.x + this->xWordWidget;\n   *y = textblock->lineYOffsetCanvas (line);\n   *width = textblock->words->getRef(wordNo)->size.width;\n   *height = line->borderAscent + line->borderDescent;\n}\n\nvoid Textblock::WordImgRenderer::getRefArea (int *xRef, int *yRef,\n                                             int *widthRef, int *heightRef)\n{\n   // See comment in Widget::drawBox about the reference area.\n   textblock->getPaddingArea (xRef, yRef, widthRef, heightRef);\n}\n\ncore::style::Style *Textblock::WordImgRenderer::getStyle ()\n{\n   return textblock->words->getRef(wordNo)->style;\n}\n\nvoid Textblock::WordImgRenderer::draw (int x, int y, int width, int height)\n{\n   textblock->queueDrawArea (x - textblock->allocation.x,\n                             y - textblock->allocation.y, width, height);\n\n}\n\nvoid Textblock::SpaceImgRenderer::getBgArea (int *x, int *y, int *width,\n                                             int *height)\n{\n   WordImgRenderer::getBgArea (x, y, width, height);\n   *x += *width;\n   *width = textblock->words->getRef(wordNo)->effSpace;\n}\n\ncore::style::Style *Textblock::SpaceImgRenderer::getStyle ()\n{\n   return textblock->words->getRef(wordNo)->spaceStyle;\n}\n\n// ----------------------------------------------------------------------\n\nTextblock::DivChar Textblock::divChars[NUM_DIV_CHARS] = {\n   // soft hyphen (U+00AD)\n   { \"\\xc2\\xad\", true, false, true, PENALTY_HYPHEN, -1 },\n   // simple hyphen-minus: same penalties like automatic or soft hyphens\n   { \"-\", false, true, true, -1, PENALTY_HYPHEN },\n   // (unconditional) hyphen (U+2010): handled exactly like minus-hyphen.\n   { \"\\xe2\\x80\\x90\", false, true, true, -1, PENALTY_HYPHEN },\n   // em dash (U+2014): breaks on both sides are allowed (but see below).\n   { \"\\xe2\\x80\\x94\", false, true, false,\n     PENALTY_EM_DASH_LEFT, PENALTY_EM_DASH_RIGHT },\n   // simple dot: same penalties like automatic or soft hyphens\n   { \".\", false, true, true, -1, PENALTY_HYPHEN },\n   // simple slash: same penalties like automatic or soft hyphens\n   { \"/\", false, true, true, -1, PENALTY_HYPHEN }\n};\n\n// Standard values are defined here. The values are already multiplied\n// with 100.\n//\n// Some examples (details are described in doc/dw-line-breaking.doc):\n//\n// 0 = Perfect line; as penalty used for normal spaces.\n\n// 1 (100 here) = A justified line with spaces having 150% or 67% of\n//                the ideal space width has this as badness.\n//\n// 8 (800 here) = A justified line with spaces twice as wide as\n//                ideally has this as badness.\n//\n// The second value is used when the line before ends with a hyphen,\n// dash etc.\n\nint Textblock::penalties[PENALTY_NUM][2] = {\n   // Penalties for all hyphens.\n   { 100, 800 },\n   // Penalties for a break point *left* of an em-dash: rather large,\n   // so that a break on the *right* side is preferred.\n   { 800, 800 },\n   // Penalties for a break point *right* of an em-dash: like hyphens.\n   { 100, 800 }\n};\n\nint Textblock::stretchabilityFactor = 100;\n\n/**\n * The character which is used to draw a hyphen at the end of a line,\n * either caused by automatic hyphenation, or by soft hyphens.\n *\n * Initially, soft hyphens were used, but they are not drawn on some\n * platforms. Also, unconditional hyphens (U+2010) are not available\n * in many fonts; so, a simple hyphen-minus is used.\n */\nconst char *Textblock::hyphenDrawChar = \"-\";\n\nvoid Textblock::setPenaltyHyphen (int penaltyHyphen)\n{\n   penalties[PENALTY_HYPHEN][0] = penaltyHyphen;\n}\n\nvoid Textblock::setPenaltyHyphen2 (int penaltyHyphen2)\n{\n   penalties[PENALTY_HYPHEN][1] = penaltyHyphen2;\n}\n\nvoid Textblock::setPenaltyEmDashLeft (int penaltyLeftEmDash)\n{\n   penalties[PENALTY_EM_DASH_LEFT][0] = penaltyLeftEmDash;\n   penalties[PENALTY_EM_DASH_LEFT][1] = penaltyLeftEmDash;\n}\n\nvoid Textblock::setPenaltyEmDashRight (int penaltyRightEmDash)\n{\n   penalties[PENALTY_EM_DASH_RIGHT][0] = penaltyRightEmDash;\n}\n\nvoid Textblock::setPenaltyEmDashRight2 (int penaltyRightEmDash2)\n{\n   penalties[PENALTY_EM_DASH_RIGHT][1] = penaltyRightEmDash2;\n}\n\nvoid Textblock::setStretchabilityFactor (int stretchabilityFactor)\n{\n   Textblock::stretchabilityFactor = stretchabilityFactor;\n}\n\nTextblock::Textblock (bool limitTextWidth, bool treatAsInline)\n{\n   DBG_OBJ_CREATE (\"dw::Textblock\");\n   registerName (\"dw::Textblock\", &CLASS_ID);\n   setButtonSensitive(true);\n\n   hasListitemValue = false;\n   leftInnerPadding = 0;\n   line1Offset = 0;\n   ignoreLine1OffsetSometimes = false;\n   mustQueueResize = false;\n   DBG_OBJ_SET_BOOL (\"mustQueueResize\", mustQueueResize);\n   redrawY = 0;\n   DBG_OBJ_SET_NUM (\"redrawY\", redrawY);\n   lastWordDrawn = -1;\n   DBG_OBJ_SET_NUM (\"lastWordDrawn\", lastWordDrawn);\n\n   DBG_OBJ_ASSOC_CHILD (&sizeRequestParams);\n        \n   /*\n    * The initial sizes of lines and words should not be\n    * too high, since this will waste much memory with tables\n    * containing many small cells. The few more calls to realloc\n    * should not decrease the speed considerably.\n    * (Current setting is for minimal memory usage. An interesting fact\n    * is that high values decrease speed due to memory handling overhead!)\n    * TODO: Some tests would be useful.\n    */\n   paragraphs = new misc::SimpleVector <Paragraph> (1);\n   lines = new misc::SimpleVector <Line> (1);\n   nonTemporaryLines = 0;\n   words = new misc::NotSoSimpleVector <Word> (1);\n   anchors = new misc::SimpleVector <Anchor> (1);\n\n   wrapRefLines = wrapRefParagraphs = -1;\n   wrapRefLinesFCX = wrapRefLinesFCY = -1;\n\n   DBG_OBJ_SET_NUM (\"lines.size\", lines->size ());\n   DBG_OBJ_SET_NUM (\"words.size\", words->size ());\n   DBG_OBJ_SET_NUM (\"wrapRefLines\", wrapRefLines);\n   DBG_OBJ_SET_NUM (\"wrapRefParagraphs\", wrapRefParagraphs);\n   DBG_OBJ_SET_NUM (\"wrapRefLinesFCX\", wrapRefLinesFCX);\n   DBG_OBJ_SET_NUM (\"wrapRefLinesFCY\", wrapRefLinesFCY);\n\n   hoverLink = -1;\n\n   // -1 means undefined.\n   lineBreakWidth = -1;\n   DBG_OBJ_SET_NUM (\"lineBreakWidth\", lineBreakWidth);\n\n   this->limitTextWidth = limitTextWidth;\n   this->treatAsInline = treatAsInline;\n\n   for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) {\n      /* hlStart[layer].index > hlEnd[layer].index means no highlighting */\n      hlStart[layer].index = 1;\n      hlStart[layer].nChar = 0;\n      hlEnd[layer].index = 0;\n      hlEnd[layer].nChar = 0;\n\n      DBG_OBJ_ARRATTRSET_NUM (\"hlStart\", layer, \"index\", hlStart[layer].index);\n      DBG_OBJ_ARRATTRSET_NUM (\"hlStart\", layer, \"nChar\", hlStart[layer].nChar);\n      DBG_OBJ_ARRATTRSET_NUM (\"hlEnd\", layer, \"index\", hlEnd[layer].index);\n      DBG_OBJ_ARRATTRSET_NUM (\"hlEnd\", layer, \"nChar\", hlEnd[layer].nChar);\n   }\n\n   numSizeReferences = 0;\n\n   initNewLine ();\n}\n\nTextblock::~Textblock ()\n{\n   /* make sure not to call a free'd tooltip (very fast overkill) */\n   hoverTooltip = NULL;\n\n   for (int i = 0; i < words->size(); i++)\n      cleanupWord (i);\n\n   for (int i = 0; i < anchors->size(); i++) {\n      Anchor *anchor = anchors->getRef (i);\n      /* This also frees the names (see removeAnchor() and related). */\n      removeAnchor(anchor->name);\n   }\n\n   delete paragraphs;\n   delete lines;\n   delete words;\n   delete anchors;\n \n   /* Make sure we don't own widgets anymore. Necessary before call of\n      parent class destructor. (???) */\n   words = NULL;\n\n   DBG_OBJ_DELETE ();\n}\n\n/**\n * The ascent of a textblock is the ascent of the first line, plus\n * padding/border/margin. This can be used to align the first lines\n * of several textblocks in a horizontal line.\n */\nvoid Textblock::sizeRequestImpl (core::Requisition *requisition, int numPos,\n                                 Widget **references, int *x, int *y)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"sizeRequestImpl\", \"%d, ...\", numPos);\n  \n   sizeRequestParams.fill (numPos, references, x, y);\n\n   // We have to rewrap the whole textblock, if (i) the available width (which\n   // is the line break width) has changed, or (ii) if the position within the\n   // float container, and so possibly borders relative to this textblock, have\n   // changed.\n   //\n   // (The latter is a simplification: an over-correct implementation would test\n   // all OOFMs on whether affectsLeftBorder() or affectsRightBorder() returns\n   // true. Also, this may be optimized by distinguishing between floats\n   // generated by this textblock (which would not make rewrapping necessary)\n   // and floats generated by other textblocks (which would).)\n   \n   int newLineBreakWidth = getAvailWidth (true);\n   int newFCX, newFCY;\n   bool fcDefined = findSizeRequestReference (OOFM_FLOATS, &newFCX, &newFCY);\n   \n   if (newLineBreakWidth != lineBreakWidth ||\n       (fcDefined && (newFCX != wrapRefLinesFCX ||\n                      newFCY != wrapRefLinesFCY))) {\n      lineBreakWidth = newLineBreakWidth;\n      wrapRefLines = 0;\n      DBG_OBJ_SET_NUM (\"lineBreakWidth\", lineBreakWidth);\n      DBG_OBJ_SET_NUM (\"wrapRefLines\", wrapRefLines);\n\n      if (!fcDefined) {\n         wrapRefLinesFCX = newFCX;\n         wrapRefLinesFCY = newFCY;\n         DBG_OBJ_SET_NUM (\"wrapRefLinesFCX\", wrapRefLinesFCX);\n         DBG_OBJ_SET_NUM (\"wrapRefLinesFCY\", wrapRefLinesFCY);\n      }\n   }\n\n   rewrap ();\n   showMissingLines ();\n\n   if (lines->size () > 0) {\n      Line *firstLine = lines->getRef(0), *lastLine = lines->getLastRef ();\n\n      // Note: the breakSpace of the last line is ignored, so breaks\n      // at the end of a textblock are not visible.\n\n      requisition->width =\n         lastLine->maxLineWidth + leftInnerPadding + boxDiffWidth ();\n\n      // Also regard collapsing of this widget top margin and the top\n      // margin of the first line box:\n      requisition->ascent = calcVerticalBorder (getStyle()->padding.top,\n                                                getStyle()->borderWidth.top,\n                                                getStyle()->margin.top\n                                                + extraSpace.top,\n                                                firstLine->borderAscent,\n                                                firstLine->marginAscent);\n\n      // And here, regard collapsing of this widget bottom margin and the\n      // bottom margin of the last line box:\n      requisition->descent =\n         // (BTW, this line:\n         lastLine->top - firstLine->borderAscent + lastLine->borderAscent +\n         // ... is 0 for a block with one line, so special handling\n         // for this case is not necessary.)\n         calcVerticalBorder (getStyle()->padding.bottom,\n                             getStyle()->borderWidth.bottom,\n                             getStyle()->margin.bottom + extraSpace.bottom,\n                             lastLine->borderDescent, lastLine->marginDescent);\n   } else {\n      requisition->width = leftInnerPadding + boxDiffWidth ();\n      requisition->ascent = boxOffsetY ();\n      requisition->descent = boxRestHeight ();\n   }\n\n   if (usesMaxGeneratorWidth ()) {\n      DBG_OBJ_MSGF (\"resize\", 1,\n                    \"before considering lineBreakWidth (= %d): %d * (%d + %d)\",\n                    lineBreakWidth, requisition->width, requisition->ascent,\n                    requisition->descent);\n      if (requisition->width < lineBreakWidth)\n         requisition->width = lineBreakWidth;\n   } else\n      DBG_OBJ_MSG (\"resize\", 1, \"lineBreakWidth needs no consideration\");\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"before correction: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n\n   correctRequisition (requisition, core::splitHeightPreserveAscent, true,\n                       false);\n\n   // Dealing with parts out of flow, which may overlap the borders of\n   // the text block. Base lines are ignored here: they do not play a\n   // role (currently) and caring about them (for the future) would\n   // cause too much problems.\n\n   // Notice that the order is not typical: correctRequisition should\n   // be the last call. However, calling correctRequisition after\n   // outOfFlowMgr->getSize may result again in a size which is too\n   // small for floats, so triggering again (and again) the resize\n   // idle function resulting in CPU hogging. See also\n   // getExtremesImpl.\n   //\n   // Is this really what we want? An alternative could be that\n   // OutOfFlowMgr::getSize honours CSS attributes an corrected sizes.\n\n   correctRequisitionByOOF (requisition, core::splitHeightPreserveAscent);\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"final: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\nint Textblock::numSizeRequestReferences ()\n{\n   return numSizeReferences;\n}\n\ncore::Widget *Textblock::sizeRequestReference (int index)\n{\n   return sizeReferences[index];\n}\n\nint Textblock::calcVerticalBorder (int widgetPadding, int widgetBorder,\n                                   int widgetMargin, int lineBorderTotal,\n                                   int lineMarginTotal)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcVerticalBorder\", \"%d, %d, %d, %d, %d\",\n                  widgetPadding, widgetBorder, widgetMargin, lineBorderTotal,\n                  lineMarginTotal);\n   \n   int result;\n   \n   if (widgetPadding == 0 && widgetBorder == 0) {\n      if (lineMarginTotal - lineBorderTotal >= widgetMargin)\n         result = lineMarginTotal;\n      else\n         result = widgetMargin + lineBorderTotal;\n   } else\n      result = lineMarginTotal + widgetPadding + widgetBorder + widgetMargin;\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", result);\n   return result;\n}\n\n/**\n * Get the extremes of a word within a textblock.\n */\nvoid Textblock::getWordExtremes (Word *word, core::Extremes *extremes)\n{\n   if (word->content.type == core::Content::WIDGET_IN_FLOW)\n      word->content.widget->getExtremes (extremes);\n   else\n      extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth =\n         extremes->maxWidthIntrinsic = extremes->adjustmentWidth =\n         word->size.width;\n}\n\nvoid Textblock::getExtremesSimpl (core::Extremes *extremes)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"getExtremesSimpl\");\n\n   fillParagraphs ();\n\n   if (paragraphs->size () == 0) {\n      /* empty page */\n      extremes->minWidth = 0;\n      extremes->minWidthIntrinsic = 0;\n      extremes->maxWidth = 0;\n      extremes->maxWidthIntrinsic = 0;\n      extremes->adjustmentWidth = 0;\n   } else  {\n      Paragraph *lastPar = paragraphs->getLastRef ();\n      extremes->minWidth = lastPar->maxParMin;\n      extremes->minWidthIntrinsic = lastPar->maxParMinIntrinsic;\n      extremes->maxWidth = lastPar->maxParMax;\n      extremes->maxWidthIntrinsic = lastPar->maxParMaxIntrinsic;\n      extremes->adjustmentWidth = lastPar->maxParAdjustmentWidth;\n\n      DBG_OBJ_MSGF (\"resize\", 1, \"paragraphs[%d]->maxParMin = %d (%d)\",\n                    paragraphs->size () - 1, lastPar->maxParMin,\n                    lastPar->maxParMinIntrinsic);\n      DBG_OBJ_MSGF (\"resize\", 1, \"paragraphs[%d]->maxParMax = %d (%d)\",\n                    paragraphs->size () - 1, lastPar->maxParMax,\n                    lastPar->maxParMaxIntrinsic);\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 0, \"after considering paragraphs: %d (%d) / %d (%d)\",\n                 extremes->minWidth, extremes->minWidthIntrinsic,\n                 extremes->maxWidth, extremes->maxWidthIntrinsic);\n\n   int diff = leftInnerPadding + boxDiffWidth ();\n   extremes->minWidth += diff;\n   extremes->minWidthIntrinsic += diff;\n   extremes->maxWidth += diff;\n   extremes->maxWidthIntrinsic += diff;\n   extremes->adjustmentWidth += diff;\n\n   DBG_OBJ_MSGF (\"resize\", 0, \"after adding diff: %d (%d) / %d (%d)\",\n                 extremes->minWidth, extremes->minWidthIntrinsic,\n                 extremes->maxWidth, extremes->maxWidthIntrinsic);\n\n   // For the order, see similar reasoning in sizeRequestImpl.\n\n   correctExtremes (extremes, true);\n\n   DBG_OBJ_MSGF (\"resize\", 0, \"after correction: %d (%d) / %d (%d)\",\n                 extremes->minWidth, extremes->minWidthIntrinsic,\n                 extremes->maxWidth, extremes->maxWidthIntrinsic);\n\n   correctExtremesByOOF (extremes);\n\n   DBG_OBJ_MSGF (\"resize\", 0,\n                 \"finally, after considering OOFM: %d (%d) / %d (%d)\",\n                 extremes->minWidth, extremes->minWidthIntrinsic,\n                 extremes->maxWidth, extremes->maxWidthIntrinsic);\n   DBG_OBJ_LEAVE ();\n}\n\nint Textblock::numGetExtremesReferences ()\n{\n   return numSizeReferences;\n}\n\ncore::Widget *Textblock::getExtremesReference (int index)\n{\n   return sizeReferences[index];\n}\n\nvoid Textblock::notifySetAsTopLevel ()\n{\n   OOFAwareWidget::notifySetAsTopLevel ();\n\n   numSizeReferences = 0;\n   DBG_OBJ_SET_NUM (\"numSizeReferences\", numSizeReferences);\n}\n\nvoid Textblock::notifySetParent ()\n{\n   OOFAwareWidget::notifySetParent ();\n\n   numSizeReferences = 0;\n   for (int i = 0; i < NUM_OOFM; i++) {\n      if (oofContainer[i] != this) {\n         // avoid dublicates\n         bool found = false;\n         for (int j = 0; !found && j < numSizeReferences; j++)\n            if (oofContainer[i] == oofContainer[j])\n               found = true;\n\n         if (!found)\n            sizeReferences[numSizeReferences++] = oofContainer[i];\n      }\n   }\n\n   DBG_OBJ_SET_NUM (\"numSizeReferences\", numSizeReferences);\n   for (int i = 0; i < numSizeReferences; i++)\n      DBG_OBJ_ARRSET_PTR (\"sizeReferences\", i, sizeReferences[i]);\n}\n\nvoid Textblock::sizeAllocateImpl (core::Allocation *allocation)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"sizeAllocateImpl\", \"%d, %d; %d * (%d + %d)\",\n                  allocation->x, allocation->y, allocation->width,\n                  allocation->ascent, allocation->descent);\n\n   showMissingLines ();\n\n   sizeAllocateStart (allocation);\n\n   int lineIndex, wordIndex;\n   Line *line;\n   Word *word;\n   int xCursor;\n   core::Allocation childAllocation;\n   core::Allocation *oldChildAllocation;\n\n   if (allocation->x != this->allocation.x ||\n       allocation->y != this->allocation.y ||\n       allocation->width != this->allocation.width) {\n      redrawY = 0;\n      DBG_OBJ_SET_NUM (\"redrawY\", redrawY);\n   }\n\n   DBG_OBJ_MSG_START ();\n\n   for (lineIndex = 0; lineIndex < lines->size (); lineIndex++) {\n      DBG_OBJ_MSGF (\"resize\", 1, \"line %d\", lineIndex);\n      DBG_OBJ_MSG_START ();\n\n      // Especially for floats, allocation->width may be different\n      // from the line break width, so that for centered and right\n      // text, the offsets have to be recalculated again. However, if\n      // the allocation width is greater than the line break width,\n      // due to wide unbreakable lines (large image etc.), use the\n      // original line break width.\n      //\n      // TODO: test case?\n      \n      calcTextOffset (lineIndex, misc::min (allocation->width, lineBreakWidth));\n\n      line = lines->getRef (lineIndex);\n      xCursor = line->textOffset;\n\n      DBG_OBJ_MSGF (\"resize\", 1, \"xCursor = %d (initially)\", xCursor);\n\n      for (wordIndex = line->firstWord; wordIndex <= line->lastWord;\n           wordIndex++) {\n         word = words->getRef (wordIndex);\n\n         if (wordIndex == lastWordDrawn + 1) {\n            redrawY = misc::min (redrawY, lineYOffsetWidget (line, allocation));\n            DBG_OBJ_SET_NUM (\"redrawY\", redrawY);\n         }\n\n         if (word->content.type == core::Content::WIDGET_IN_FLOW) {\n            DBG_OBJ_MSGF (\"resize\", 1,\n                          \"allocating widget in flow: line %d, word %d\",\n                          lineIndex, wordIndex);\n\n            // TODO For word->flags & Word::TOPLEFT_OF_LINE, make\n            // allocation consistent with calcSizeOfWidgetInFlow():\n\n            childAllocation.x = xCursor + allocation->x;\n            \n            /** \\todo Justification within the line is done here. */\n            /* align=top:\n               childAllocation.y = line->top + allocation->y;\n            */\n\n            /* align=bottom (base line) */\n            /* Commented lines break the n2 and n3 test cases at\n             * https://dillo-browser.github.io/old/test/img/ */\n            childAllocation.y = lineYOffsetCanvas (line, allocation)\n               + (line->borderAscent - word->size.ascent);\n\n            childAllocation.width = word->size.width;\n            childAllocation.ascent = word->size.ascent;\n            childAllocation.descent = word->size.descent;\n \n            oldChildAllocation = word->content.widget->getAllocation();\n\n            if (childAllocation.x != oldChildAllocation->x ||\n               childAllocation.y != oldChildAllocation->y ||\n               childAllocation.width != oldChildAllocation->width) {\n               /* The child widget has changed its position or its width\n                * so we need to redraw from this line onwards.\n                */\n               redrawY =\n                  misc::min (redrawY, lineYOffsetWidget (line, allocation));\n               DBG_OBJ_SET_NUM (\"redrawY\", redrawY);\n               if (word->content.widget->wasAllocated ()) {\n                  redrawY = misc::min (redrawY,\n                     oldChildAllocation->y - this->allocation.y);\n                  DBG_OBJ_SET_NUM (\"redrawY\", redrawY);\n               }\n\n            } else if (childAllocation.ascent + childAllocation.descent !=\n               oldChildAllocation->ascent + oldChildAllocation->descent) {\n               /* The child widget has changed its height. We need to redraw\n                * from where it changed.\n                * It's important not to draw from the line base, because the\n                * child might be a table covering the whole page so we would\n                * end up redrawing the whole screen over and over.\n                * The drawing of the child content is left to the child itself.\n                * However this optimization is only possible if the widget is\n                * the only word in the line apart from an optional BREAK.\n                * Otherwise the height change of the widget could change the\n                * position of other words in the line, requiring a\n                * redraw of the complete line.\n                */\n               if (line->lastWord == line->firstWord ||\n                   (line->lastWord == line->firstWord + 1 &&\n                    words->getRef (line->lastWord)->content.type ==\n                    core::Content::BREAK)) {\n\n                  int childChangedY =\n                     misc::min(childAllocation.y - allocation->y +\n                        childAllocation.ascent + childAllocation.descent,\n                        oldChildAllocation->y - this->allocation.y +\n                        oldChildAllocation->ascent +\n                        oldChildAllocation->descent);\n\n                  redrawY = misc::min (redrawY, childChangedY);\n                  DBG_OBJ_SET_NUM (\"redrawY\", redrawY);\n               } else {\n                  redrawY =\n                     misc::min (redrawY, lineYOffsetWidget (line, allocation));\n                  DBG_OBJ_SET_NUM (\"redrawY\", redrawY);\n               }\n            }\n            word->content.widget->sizeAllocate (&childAllocation);\n         }\n\n         xCursor += (word->size.width + word->effSpace);\n         DBG_OBJ_MSGF (\"resize\", 1, \"xCursor = %d (after word %d)\",\n                       xCursor, wordIndex);\n         DBG_MSG_WORD (\"resize\", 1, \"<i>that is:</i> \", wordIndex, \"\");\n      }\n\n      DBG_OBJ_MSG_END ();\n   }\n\n   DBG_OBJ_MSG_END ();\n\n   sizeAllocateEnd ();\n      \n   for (int i = 0; i < anchors->size(); i++) {\n      Anchor *anchor = anchors->getRef(i);\n      int y;\n\n      if (anchor->wordIndex >= words->size() ||\n          // Also regard not-yet-existing lines.\n          lines->size () <= 0 ||\n          anchor->wordIndex > lines->getLastRef()->lastWord) {\n         y = allocation->y + allocation->ascent + allocation->descent;\n      } else {\n         Line *line = lines->getRef(findLineOfWord (anchor->wordIndex));\n         y = lineYOffsetCanvas (line, allocation);\n      }\n      changeAnchor (anchor->name, y);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::calcExtraSpaceImpl (int numPos, Widget **references, int *x,\n                                    int *y)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"Textblock::calcExtraSpaceImpl\");\n\n   sizeRequestParams.fill (numPos, references, x, y);\n\n   OOFAwareWidget::calcExtraSpaceImpl (numPos, references, x, y);\n\n   int clearPosition = 0;\n   for (int i = 0; i < NUM_OOFM; i++)\n      if (searchOutOfFlowMgr (i) && findSizeRequestReference (i, NULL, NULL))\n         clearPosition =\n            misc::max (clearPosition,\n                       searchOutOfFlowMgr(i)->getClearPosition (this));\n   \n   extraSpace.top = misc::max (extraSpace.top, clearPosition);\n\n   DBG_OBJ_LEAVE ();\n}\n\nint Textblock::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"Textblock::getAvailWidthOfChild\", \"%p, %s\",\n                  child, forceValue ? \"true\" : \"false\");\n\n   int width;\n\n   if (isWidgetOOF (child) && getWidgetOutOfFlowMgr(child) &&\n       getWidgetOutOfFlowMgr(child)->dealingWithSizeOfChild (child))\n      width =\n         getWidgetOutOfFlowMgr(child)->getAvailWidthOfChild (child,forceValue);\n   else {\n      if (child->getStyle()->width == core::style::LENGTH_AUTO) {\n         // No width specified: similar to standard implementation (see\n         // there), but \"leftInnerPadding\" has to be considered, too.\n         DBG_OBJ_MSG (\"resize\", 1, \"no specification\");\n         if (forceValue)\n            width = misc::max (getAvailWidth (true) - boxDiffWidth ()\n                               - leftInnerPadding,\n                               0);\n         else\n            width = -1;\n      } else\n         width = Widget::getAvailWidthOfChild (child, forceValue);\n      \n      if (forceValue && this == child->getContainer () &&\n          !usesMaxGeneratorWidth ()) {\n         core::Extremes extremes;\n         getExtremes (&extremes);\n         if (width > extremes.maxWidth - boxDiffWidth () - leftInnerPadding)\n            width = extremes.maxWidth - boxDiffWidth () - leftInnerPadding;\n      }\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", width);\n   return width;\n}\n\nint Textblock::getAvailHeightOfChild (core::Widget *child, bool forceValue)\n{\n   if (isWidgetOOF(child) && getWidgetOutOfFlowMgr(child) &&\n       getWidgetOutOfFlowMgr(child)->dealingWithSizeOfChild (child))\n      return getWidgetOutOfFlowMgr(child)->getAvailHeightOfChild (child,\n                                                                  forceValue);\n   else\n      return Widget::getAvailHeightOfChild (child, forceValue);\n}\n\nvoid Textblock::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n\n   for (int i = 0; i < words->size (); i++) {\n      Word *word = words->getRef (i);\n      if (word->content.type == core::Content::WIDGET_IN_FLOW)\n         word->content.widget->containerSizeChanged ();\n   }\n\n   containerSizeChangedForChildrenOOF ();\n   \n   DBG_OBJ_LEAVE ();\n}\n\nbool Textblock::affectsSizeChangeContainerChild (Widget *child)\n{\n   DBG_OBJ_ENTER (\"resize\", 0,\n                  \"Textblock/affectsSizeChangeContainerChild\", \"%p\", child);\n\n   // See Textblock::getAvailWidthOfChild() and Textblock::oofSizeChanged():\n   // Extremes changes affect the size of the child, too:\n   bool ret;\n   if (!usesMaxGeneratorWidth () &&\n       (extremesQueued () || extremesChanged ()))\n      ret = true;\n   else\n      ret = Widget::affectsSizeChangeContainerChild (child);\n\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr(ret));\n   return ret;\n}\n\nbool Textblock::usesAvailWidth ()\n{\n   return true;\n}\n\nvoid Textblock::resizeDrawImpl ()\n{\n   DBG_OBJ_ENTER0 (\"draw\", 0, \"resizeDrawImpl\");\n\n   queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY);\n   if (lines->size () > 0) {\n      Line *lastLine = lines->getRef (lines->size () - 1);\n      /* Remember the last word that has been drawn so we can ensure to\n       * draw any new added words (see sizeAllocateImpl()).\n       */\n      lastWordDrawn = lastLine->lastWord;\n      DBG_OBJ_SET_NUM (\"lastWordDrawn\", lastWordDrawn);\n   }\n\n   redrawY = getHeight ();\n   DBG_OBJ_SET_NUM (\"redrawY\", redrawY);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::markSizeChange (int ref)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"markSizeChange\", \"%d\", ref);\n\n   if (isParentRefOOF (ref))\n      getParentRefOutOfFlowMgr(ref)\n         ->markSizeChange (getParentRefOOFSubRef (ref));\n   else {\n      /* By the way: ref == -1 may have two different causes: (i) flush()\n         calls \"queueResize (-1, true)\", when no rewrapping is necessary;\n         and (ii) a word may have parentRef == -1 , when it is not yet\n         added to a line.  In the latter case, nothing has to be done\n         now, but addLine(...) will do everything necessary. */\n      if (ref != -1) {\n         if (wrapRefLines == -1)\n            wrapRefLines = getParentRefInFlowSubRef (ref);\n         else\n            wrapRefLines = misc::min (wrapRefLines,\n                                      getParentRefInFlowSubRef (ref));\n      }\n\n      DBG_OBJ_SET_NUM (\"wrapRefLines\", wrapRefLines);\n\n      // It seems that sometimes (even without floats) the lines\n      // structure is changed, so that wrapRefLines may refers to a\n      // line which does not exist anymore. Should be examined\n      // again. Until then, setting wrapRefLines to the same value is\n      // a workaround.\n      markExtremesChange (ref);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::markExtremesChange (int ref)\n{\n   DBG_OBJ_ENTER (\"resize\", 1, \"markExtremesChange\", \"%d\", ref);\n\n   if (isParentRefOOF (ref))\n      getParentRefOutOfFlowMgr(ref)\n         ->markExtremesChange (getParentRefOOFSubRef (ref));\n   else {\n      /* By the way: ref == -1 may have two different causes: (i) flush()\n         calls \"queueResize (-1, true)\", when no rewrapping is necessary;\n         and (ii) a word may have parentRef == -1 , when it is not yet\n         added to a line.  In the latter case, nothing has to be done\n         now, but addLine(...) will do everything necessary. */\n      if (ref != -1) {\n         if (wrapRefParagraphs == -1)\n            wrapRefParagraphs = getParentRefInFlowSubRef (ref);\n         else\n            wrapRefParagraphs =\n               misc::min (wrapRefParagraphs, getParentRefInFlowSubRef (ref));\n      }\n\n      DBG_OBJ_SET_NUM (\"wrapRefParagraphs\", wrapRefParagraphs);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nbool Textblock::isBlockLevel ()\n{\n   return true;\n}\n\nbool Textblock::buttonPressImpl (core::EventButton *event)\n{\n   return sendSelectionEvent (core::SelectionState::BUTTON_PRESS, event);\n}\n\nbool Textblock::buttonReleaseImpl (core::EventButton *event)\n{\n   return sendSelectionEvent (core::SelectionState::BUTTON_RELEASE, event);\n}\n\n/*\n * Handle motion inside the widget\n * (special care is necessary when switching from another widget,\n *  because hoverLink and hoverTooltip are meaningless then).\n */\nbool Textblock::motionNotifyImpl (core::EventMotion *event)\n{\n   if (event->state & core::BUTTON1_MASK)\n      return sendSelectionEvent (core::SelectionState::BUTTON_MOTION, event);\n   else {\n      bool inSpace;\n      int linkOld = hoverLink;\n      core::style::Tooltip *tooltipOld = hoverTooltip;\n      const Word *word = findWord (event->xWidget, event->yWidget, &inSpace);\n\n      // cursor from word or widget style\n      if (word == NULL) {\n         setCursor (getStyle()->cursor);\n         hoverLink = -1;\n         hoverTooltip = NULL;\n      } else {\n         core::style::Style *style = inSpace ? word->spaceStyle : word->style;\n         setCursor (style->cursor);\n         hoverLink = style->x_link;\n         hoverTooltip = style->x_tooltip;\n      }\n\n      // Show/hide tooltip\n      if (tooltipOld != hoverTooltip) {\n         if (tooltipOld)\n            tooltipOld->onLeave ();\n         if (hoverTooltip)\n            hoverTooltip->onEnter ();\n      } else if (hoverTooltip)\n         hoverTooltip->onMotion ();\n\n      _MSG(\"MN tb=%p tooltipOld=%p hoverTooltip=%p\\n\",\n          this, tooltipOld, hoverTooltip);\n      if (hoverLink != linkOld) {\n         /* LinkEnter with hoverLink == -1 is the same as LinkLeave */\n         return layout->emitLinkEnter (this, hoverLink, -1, -1, -1);\n      } else {\n         return hoverLink != -1;\n      }\n   }\n}\n\nvoid Textblock::enterNotifyImpl (core::EventCrossing *event)\n{\n   _MSG(\" tb=%p, ENTER NotifyImpl hoverTooltip=%p\\n\", this, hoverTooltip);\n   /* reset hoverLink so linkEnter is detected */\n   hoverLink = -2;\n}\n\nvoid Textblock::leaveNotifyImpl (core::EventCrossing *event)\n{\n   _MSG(\" tb=%p, LEAVE NotifyImpl: hoverTooltip=%p\\n\", this, hoverTooltip);\n\n   /* leaving the viewport can't be handled by motionNotifyImpl() */\n   if (hoverLink >= 0)\n      layout->emitLinkEnter (this, -1, -1, -1, -1);\n\n   if (hoverTooltip) {\n      hoverTooltip->onLeave();\n      hoverTooltip = NULL;\n   }\n}\n\n/**\n * \\brief Send event to selection.\n */\nbool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType,\n                                    core::MousePositionEvent *event)\n{\n   DBG_OBJ_ENTER0 (\"events\", 0, \"sendSelectionEvent\");\n\n   core::Iterator *it;\n   int wordIndex;\n   int charPos = 0, link = -1;\n   bool r;\n\n   if (words->size () == 0) {\n      wordIndex = -1;\n   } else {\n      Line *lastLine = lines->getRef (lines->size () - 1);\n      int yFirst = lineYOffsetCanvas (0);\n      int yLast = lineYOffsetCanvas (lastLine) + lastLine->borderAscent +\n                  lastLine->borderDescent;\n      if (event->yCanvas < yFirst) {\n         // Above the first line: take the first word.\n         wordIndex = 0;\n      } else if (event->yCanvas >= yLast) {\n         // Below the last line: take the last word.\n         wordIndex = words->size () - 1;\n         charPos = core::SelectionState::END_OF_WORD;\n      } else {\n         Line *line =\n            lines->getRef (findLineIndexWhenAllocated (event->yWidget));\n\n         // Pointer within the break space?\n         if (event->yWidget > (lineYOffsetWidget (line) +\n                               line->borderAscent + line->borderDescent)) {\n            // Choose this break.\n            wordIndex = line->lastWord;\n            charPos = core::SelectionState::END_OF_WORD;\n         } else if (event->xWidget < line->textOffset) {\n            // Left of the first word in the line.\n            wordIndex = line->firstWord;\n         } else {\n            int nextWordStartX = line->textOffset;\n\n            for (wordIndex = line->firstWord;\n                 wordIndex <= line->lastWord;\n                 wordIndex++) {\n               Word *word = words->getRef (wordIndex);\n               int wordStartX = nextWordStartX;\n\n               nextWordStartX += word->size.width + word->effSpace;\n\n               if (event->xWidget >= wordStartX &&\n                   event->xWidget < nextWordStartX) {\n                  // We have found the word.\n                  int yWidgetBase =\n                     lineYOffsetWidget (line) + line->borderAscent;\n\n                  if (event->xWidget >= nextWordStartX  - word->effSpace) {\n                     charPos = core::SelectionState::END_OF_WORD;\n                     if (wordIndex < line->lastWord &&\n                         (words->getRef(wordIndex + 1)->content.type !=\n                          core::Content::BREAK) &&\n                         (event->yWidget <=\n                          yWidgetBase + word->spaceStyle->font->descent) &&\n                         (event->yWidget >\n                          yWidgetBase - word->spaceStyle->font->ascent)) {\n                        link = word->spaceStyle->x_link;\n                     }\n                  } else {\n                     if (event->yWidget <= yWidgetBase + word->size.descent &&\n                         event->yWidget > yWidgetBase - word->size.ascent) {\n                        link = word->style->x_link;\n                     }\n                     if (word->content.type == core::Content::TEXT) {\n                        int glyphX = wordStartX;\n                        int isStartWord = word->flags & Word::WORD_START;\n                        int isEndWord = word->flags & Word::WORD_END;\n\n                        while (1) {\n                           int nextCharPos =\n                              layout->nextGlyph (word->content.text, charPos);\n                           // TODO The width of a text is not the sum\n                           // of the widths of the glyphs, because of\n                           // ligatures, kerning etc., so textWidth\n                           // should be applied to the text from 0 to\n                           // nextCharPos. (Or not? See comment below.)\n\n                           int glyphWidth =\n                              textWidth (word->content.text, charPos,\n                                         nextCharPos - charPos, word->style,\n                                         isStartWord && charPos == 0,\n                                         isEndWord &&\n                                         word->content.text[nextCharPos] == 0);\n                           if (event->xWidget > glyphX + glyphWidth) {\n                              glyphX += glyphWidth;\n                              charPos = nextCharPos;\n                              continue;\n                           } else if (event->xWidget >= glyphX + glyphWidth/2){\n                              // On the right half of a character;\n                              // now just look for combining chars\n                              charPos = nextCharPos;\n                              while (word->content.text[charPos]) {\n                                 nextCharPos =\n                                    layout->nextGlyph (word->content.text,\n                                                       charPos);\n                                 if (textWidth (word->content.text, charPos,\n                                                nextCharPos - charPos,\n                                                word->style,\n                                                isStartWord && charPos == 0,\n                                                isEndWord &&\n                                                word->content.text[nextCharPos]\n                                                == 0))\n                                    break;\n                                 charPos = nextCharPos;\n                              }\n                           }\n                           break;\n                        }\n                     } else {\n                        // Depends on whether the pointer is within the left or\n                        // right half of the (non-text) word.\n                        if (event->xWidget >= (wordStartX + nextWordStartX) /2)\n                           charPos = core::SelectionState::END_OF_WORD;\n                     }\n                  }\n                  break;\n               }\n            }\n            if (wordIndex > line->lastWord) {\n               // No word found in this line (i.e. we are on the right side),\n               // take the last of this line.\n               wordIndex = line->lastWord;\n               charPos = core::SelectionState::END_OF_WORD;\n            }\n         }\n      }\n   }\n   \n   DBG_OBJ_MSGF (\"events\", 1, \"wordIndex = %d\", wordIndex);\n   DBG_MSG_WORD (\"events\", 1, \"<i>this is:</i> \", wordIndex, \"\");\n      \n   it = TextblockIterator::createWordIndexIterator\n           (this, core::Content::maskForSelection (true), wordIndex);\n   r = selectionHandleEvent (eventType, it, charPos, link, event);\n   it->unref ();\n\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr(r));\n   return r;\n}\n\nvoid Textblock::removeChild (Widget *child)\n{\n   /** \\bug Not implemented. */\n}\n\ncore::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd)\n{\n   return new TextblockIterator (this, mask, atEnd);\n}\n\n/*\n * Draw the decorations on a word.\n */\nvoid Textblock::decorateText (core::View *view, core::style::Style *style,\n                              core::style::Color::Shading shading,\n                              int x, int yBase, int width)\n{\n   int y, height;\n\n   height = 1 + style->font->xHeight / 12;\n   if (style->textDecoration & core::style::TEXT_DECORATION_UNDERLINE) {\n      y = yBase + style->font->descent / 3;\n      view->drawRectangle (style->color, shading, true, x, y, width, height);\n   }\n   if (style->textDecoration & core::style::TEXT_DECORATION_OVERLINE) {\n      y = yBase - style->font->ascent;\n      view->drawRectangle (style->color, shading, true, x, y, width, height);\n   }\n   if (style->textDecoration & core::style::TEXT_DECORATION_LINE_THROUGH) {\n      y = yBase + (style->font->descent - style->font->ascent) / 2 +\n          style->font->descent / 4;\n      view->drawRectangle (style->color, shading, true, x, y, width, height);\n   }\n}\n\n/*\n * Draw a string of text\n *\n * Arguments: ... \"isStart\" and \"isEnd\" are true, when the text\n * start/end represents the start/end of a \"real\" text word (before\n * hyphenation). This has an effect on text transformation. (\"isEnd\"\n * is not used yet, but here for symmetry.)\n */\nvoid Textblock::drawText(core::View *view, core::style::Style *style,\n                         core::style::Color::Shading shading, int x, int y,\n                         const char *text, int start, int len, bool isStart,\n                         bool isEnd)\n{\n   if (len > 0) {\n      char *str = NULL;\n\n      switch (style->textTransform) {\n         case core::style::TEXT_TRANSFORM_NONE:\n         default:\n            break;\n         case core::style::TEXT_TRANSFORM_UPPERCASE:\n            str = layout->textToUpper(text + start, len);\n            break;\n         case core::style::TEXT_TRANSFORM_LOWERCASE:\n            str = layout->textToLower(text + start, len);\n            break;\n         case core::style::TEXT_TRANSFORM_CAPITALIZE:\n            // If \"isStart\" is false, the first letter of \"text\" is\n            // not the first letter of the \"real\" text word, so no\n            // transformation is necessary.\n            if (isStart) {\n               /* \\bug No way to know about non-ASCII punctuation. */\n               bool initial_seen = false;\n\n               for (int i = 0; i < start; i++)\n                  if (!ispunct(text[i]))\n                     initial_seen = true;\n               if (initial_seen)\n                  break;\n\n               int after = 0;\n               text += start;\n               while (ispunct(text[after]))\n                  after++;\n               if (text[after])\n                  after = layout->nextGlyph(text, after);\n               if (after > len)\n                  after = len;\n\n               char *initial = layout->textToUpper(text, after);\n               int newlen = strlen(initial) + len-after;\n               str = (char *)malloc(newlen + 1);\n               strcpy(str, initial);\n               strncpy(str + strlen(str), text+after, len-after);\n               str[newlen] = '\\0';\n               free(initial);\n            }\n            break;\n      }\n\n      view->drawText(style->font, style->color, shading, x, y,\n                     str ? str : text + start, str ? strlen(str) : len);\n      if (str)\n         free(str);\n   }\n}\n\n/**\n * Draw a word of text.\n *\n * Since hyphenated words consist of multiple instance of\n * dw::Textblock::Word, which should be drawn as a whole (to preserve\n * kerning etc.; see \\ref dw-line-breaking), two indices are\n * passed. See also drawLine(), where drawWord() is called.\n */\nvoid Textblock::drawWord (Line *line, int wordIndex1, int wordIndex2,\n                          core::View *view, core::Rectangle *area,\n                          int xWidget, int yWidgetBase)\n{\n   core::style::Style *style = words->getRef(wordIndex1)->style;\n   bool drawHyphen = wordIndex2 == line->lastWord\n      && (words->getRef(wordIndex2)->flags & Word::DIV_CHAR_AT_EOL);\n\n   if (style->hasBackground ()) {\n      int w = 0;\n      for (int i = wordIndex1; i <= wordIndex2; i++)\n         w += words->getRef(i)->size.width;\n      w += words->getRef(wordIndex2)->hyphenWidth;\n      drawBox (view, style, area, xWidget, yWidgetBase - line->borderAscent,\n               w, line->borderAscent + line->borderDescent, false);\n   }\n\n   if (wordIndex1 == wordIndex2 && !drawHyphen) {\n      // Simple case, where copying in one buffer is not needed.\n      Word *word = words->getRef (wordIndex1);\n      drawWord0 (wordIndex1, wordIndex2, word->content.text, word->size.width,\n                 false, style, view, area, xWidget, yWidgetBase);\n   } else {\n      // Concatenate all words in a new buffer.\n      int l = 0, totalWidth = 0;\n      for (int i = wordIndex1; i <= wordIndex2; i++) {\n         Word *w = words->getRef (i);\n         l += strlen (w->content.text);\n         totalWidth += w->size.width;\n      }\n\n      char text[l + (drawHyphen ? strlen (hyphenDrawChar) : 0) + 1];\n      int p = 0;\n      for (int i = wordIndex1; i <= wordIndex2; i++) {\n         const char * t = words->getRef(i)->content.text;\n         strcpy (text + p, t);\n         p += strlen (t);\n      }\n\n      if(drawHyphen) {\n         for (int i = 0; hyphenDrawChar[i]; i++)\n            text[p++] = hyphenDrawChar[i];\n         text[p++] = 0;\n      }\n\n      drawWord0 (wordIndex1, wordIndex2, text, totalWidth, drawHyphen,\n                 style, view, area, xWidget, yWidgetBase);\n   }\n}\n\n/**\n * TODO Comment\n */\nvoid Textblock::drawWord0 (int wordIndex1, int wordIndex2,\n                           const char *text, int totalWidth, bool drawHyphen,\n                           core::style::Style *style, core::View *view,\n                           core::Rectangle *area, int xWidget, int yWidgetBase)\n{\n   int xWorld = allocation.x + xWidget;\n   int yWorldBase;\n\n   /* Adjust the text baseline if the word is <SUP>-ed or <SUB>-ed. */\n   if (style->valign == core::style::VALIGN_SUB)\n      yWidgetBase += style->font->ascent / 3;\n   else if (style->valign == core::style::VALIGN_SUPER) {\n      yWidgetBase -= style->font->ascent / 2;\n   }\n   yWorldBase = yWidgetBase + allocation.y;\n\n   bool isStartTotal = words->getRef(wordIndex1)->flags & Word::WORD_START;\n   bool isEndTotal = words->getRef(wordIndex2)->flags & Word::WORD_START;\n   drawText (view, style, core::style::Color::SHADING_NORMAL, xWorld,\n             yWorldBase, text, 0, strlen (text), isStartTotal, isEndTotal);\n\n   if (style->textDecoration)\n      decorateText(view, style, core::style::Color::SHADING_NORMAL, xWorld,\n                   yWorldBase, totalWidth);\n\n   for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) {\n      if (wordIndex1 <= hlEnd[layer].index &&\n          wordIndex2 >= hlStart[layer].index) {\n         const int wordLen = strlen (text);\n         int xStart, width;\n         int firstCharIdx;\n         int lastCharIdx;\n\n         if (hlStart[layer].index < wordIndex1)\n            firstCharIdx = 0;\n         else {\n            firstCharIdx =\n               misc::min (hlStart[layer].nChar,\n                          (int)strlen (words->getRef(hlStart[layer].index)\n                                       ->content.text));\n            for (int i = wordIndex1; i < hlStart[layer].index; i++)\n               // It can be assumed that all words from wordIndex1 to\n               // wordIndex2 have content type TEXT.\n               firstCharIdx += strlen (words->getRef(i)->content.text);\n         }\n\n         if (hlEnd[layer].index > wordIndex2)\n            lastCharIdx = wordLen;\n         else {\n            lastCharIdx =\n               misc::min (hlEnd[layer].nChar,\n                          (int)strlen (words->getRef(hlEnd[layer].index)\n                                       ->content.text));\n            for (int i = wordIndex1; i < hlEnd[layer].index; i++)\n               // It can be assumed that all words from wordIndex1 to\n               // wordIndex2 have content type TEXT.\n               lastCharIdx += strlen (words->getRef(i)->content.text);\n         }\n\n         xStart = xWorld;\n         if (firstCharIdx)\n            xStart += textWidth (text, 0, firstCharIdx, style,\n                                 isStartTotal,\n                                 isEndTotal && text[firstCharIdx] == 0);\n         // With a hyphen, the width is a bit longer than totalWidth,\n         // and so, the optimization to use totalWidth is not correct.\n         if (!drawHyphen && firstCharIdx == 0 && lastCharIdx == wordLen)\n            width = totalWidth;\n         else\n            width = textWidth (text, firstCharIdx,\n                               lastCharIdx - firstCharIdx, style,\n                               isStartTotal && firstCharIdx == 0,\n                               isEndTotal && text[lastCharIdx] == 0);\n         if (width > 0) {\n            /* Highlight text */\n            core::style::Color *wordBgColor;\n\n            if (!(wordBgColor =  style->backgroundColor))\n               wordBgColor = getBgColor();\n\n            /* Draw background for highlighted text. */\n            view->drawRectangle (\n               wordBgColor, core::style::Color::SHADING_INVERSE, true, xStart,\n               yWorldBase - style->font->ascent, width,\n               style->font->ascent + style->font->descent);\n\n            /* Highlight the text. */\n            drawText (view, style, core::style::Color::SHADING_INVERSE, xStart,\n                      yWorldBase, text, firstCharIdx,\n                      lastCharIdx - firstCharIdx,\n                      isStartTotal && firstCharIdx == 0,\n                      isEndTotal && lastCharIdx == wordLen);\n\n            if (style->textDecoration)\n               decorateText(view, style, core::style::Color::SHADING_INVERSE,\n                            xStart, yWorldBase, width);\n         }\n      }\n   }\n}\n\n/*\n * Draw a space.\n */\nvoid Textblock::drawSpace(int wordIndex, core::View *view,\n                          core::Rectangle *area, int xWidget, int yWidgetBase)\n{\n   Word *word = words->getRef(wordIndex);\n   int xWorld = allocation.x + xWidget;\n   int yWorldBase;\n   core::style::Style *style = word->spaceStyle;\n   bool highlight = false;\n\n   /* Adjust the space baseline if it is <SUP>-ed or <SUB>-ed */\n   if (style->valign == core::style::VALIGN_SUB)\n      yWidgetBase += style->font->ascent / 3;\n   else if (style->valign == core::style::VALIGN_SUPER) {\n      yWidgetBase -= style->font->ascent / 2;\n   }\n   yWorldBase = allocation.y + yWidgetBase;\n\n   for (int layer = 0; layer < core::HIGHLIGHT_NUM_LAYERS; layer++) {\n      if (hlStart[layer].index <= wordIndex &&\n          hlEnd[layer].index > wordIndex) {\n         highlight = true;\n         break;\n      }\n   }\n   if (highlight) {\n      core::style::Color *spaceBgColor;\n\n      if (!(spaceBgColor = style->backgroundColor))\n         spaceBgColor = getBgColor();\n\n      view->drawRectangle (\n         spaceBgColor, core::style::Color::SHADING_INVERSE, true, xWorld,\n         yWorldBase - style->font->ascent, word->effSpace,\n         style->font->ascent + style->font->descent);\n   }\n   if (style->textDecoration) {\n      core::style::Color::Shading shading = highlight ?\n         core::style::Color::SHADING_INVERSE :\n         core::style::Color::SHADING_NORMAL;\n\n      decorateText(view, style, shading, xWorld, yWorldBase, word->effSpace);\n   }\n}\n\n/*\n * Paint a line\n * - x and y are toplevel dw coordinates (Question: what Dw? Changed. Test!)\n * - area is used always (ev. set it to event->area)\n * - event is only used when is_expose\n */\nvoid Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area,\n                          core::DrawingContext *context)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"drawLine\", \"..., [%d, %d, %d * %d]\",\n                  area->x, area->y, area->width, area->height);\n  \n   int xWidget = line->textOffset;\n   int yWidgetBase = lineYOffsetWidget (line) + line->borderAscent;\n\n   DBG_OBJ_MSGF (\"draw\", 1, \"line from %d to %d (%d words), at (%d, %d)\",\n                 line->firstWord, line->lastWord, words->size (),\n                 xWidget, yWidgetBase);\n   DBG_MSG_WORD (\"draw\", 1, \"<i>line starts with: </i>\", line->firstWord, \"\");\n   DBG_MSG_WORD (\"draw\", 1, \"<i>line ends with: </i>\", line->lastWord, \"\");\n\n   DBG_OBJ_MSG_START ();\n\n   for (int wordIndex = line->firstWord;\n        wordIndex <= line->lastWord && xWidget < area->x + area->width;\n        wordIndex++) {\n      DBG_MSG_WORD (\"draw\", 2, \"<i>drawing: </i>\", wordIndex, \"\");\n\n      Word *word = words->getRef (wordIndex);\n      int wordSize = word->size.width;\n\n      if (xWidget + wordSize + word->hyphenWidth + word->effSpace >= area->x) {\n         if (word->content.type == core::Content::TEXT ||\n             word->content.type == core::Content::WIDGET_IN_FLOW) {\n\n            if (word->size.width > 0) {\n               if (word->content.type == core::Content::WIDGET_IN_FLOW) {\n                  core::Widget *child = word->content.widget;\n                  core::Rectangle childArea;\n                  if (!core::StackingContextMgr::handledByStackingContextMgr\n                          (child) &&\n                      child->intersects (this, area, &childArea))\n                     child->draw (view, &childArea, context);\n               } else {\n                  int wordIndex2 = wordIndex;\n                  while (wordIndex2 < line->lastWord &&\n                         (words->getRef(wordIndex2)->flags\n                          & Word::DRAW_AS_ONE_TEXT) &&\n                         word->style == words->getRef(wordIndex2 + 1)->style)\n                     wordIndex2++;\n\n                  drawWord(line, wordIndex, wordIndex2, view, area,\n                           xWidget, yWidgetBase);\n                  wordSize = 0;\n                  for (int i = wordIndex; i <= wordIndex2; i++)\n                     wordSize += words->getRef(i)->size.width;\n\n                  wordIndex = wordIndex2;\n                  word = words->getRef(wordIndex);\n               }\n            }\n\n            if (word->effSpace > 0 && wordIndex < line->lastWord &&\n                words->getRef(wordIndex + 1)->content.type !=\n                                                        core::Content::BREAK) {\n               if (word->spaceStyle->hasBackground ())\n                  drawBox (view, word->spaceStyle, area,\n                           xWidget + wordSize,\n                           yWidgetBase - line->borderAscent, word->effSpace,\n                           line->borderAscent + line->borderDescent, false);\n               drawSpace (wordIndex, view, area, xWidget + wordSize,\n                          yWidgetBase);\n            }\n\n         }\n      }\n      xWidget += wordSize + word->effSpace;\n   }\n\n   DBG_OBJ_MSG_END ();\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Find the first line index that includes y, which is given in widget\n * coordinates.\n */\nint Textblock::findLineIndex (int y)\n{\n   return wasAllocated () ?\n      findLineIndexWhenAllocated (y) : findLineIndexWhenNotAllocated (y);\n}\n\nint Textblock::findLineIndexWhenNotAllocated (int y)\n{\n   if (lines->size() == 0)\n      return -1;\n   else\n      return\n         findLineIndex (y, calcVerticalBorder (getStyle()->padding.top,\n                                               getStyle()->borderWidth.top,\n                                               getStyle()->margin.top\n                                               + extraSpace.top,\n                                               lines->getRef(0)->borderAscent,\n                                               lines->getRef(0)->marginAscent));\n}\n\nint Textblock::findLineIndexWhenAllocated (int y)\n{\n   assert (wasAllocated ());\n   return findLineIndex (y, allocation.ascent);\n}\n\nint Textblock::findLineIndex (int y, int ascent)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"findLineIndex\", \"%d, %d\", y, ascent);\n\n   core::Allocation alloc;\n   alloc.ascent = ascent; // More is not needed.\n\n   int maxIndex = lines->size () - 1;\n   int step, index, low = 0;\n\n   step = (lines->size() + 1) >> 1;\n   while ( step > 1 ) {\n      index = low + step;\n      if (index <= maxIndex && lineYOffsetWidget (index, &alloc) <= y)\n         low = index;\n      step = (step + 1) >> 1;\n   }\n\n   if (low < maxIndex && lineYOffsetWidget (low + 1, &alloc) <= y)\n      low++;\n\n   /*\n    * This new routine returns the line number between (top) and\n    * (top + size.ascent + size.descent + breakSpace): the space\n    * _below_ the line is considered part of the line.  Old routine\n    * returned line number between (top - previous_line->breakSpace)\n    * and (top + size.ascent + size.descent): the space _above_ the\n    * line was considered part of the line.  This is important for\n    * Dw_page_find_link() --EG\n    * That function has now been inlined into Dw_page_motion_notify() --JV\n    */\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", low);\n   return low;\n}\n\n/**\n * \\brief Find the line of word \\em wordIndex.\n */\nint Textblock::findLineOfWord (int wordIndex)\n{\n   if (wordIndex < 0 || wordIndex >= words->size () ||\n       // Also regard not-yet-existing lines.\n       lines->size () <= 0 || wordIndex > lines->getLastRef()->lastWord)\n      return -1;\n\n   int high = lines->size () - 1, index, low = 0;\n\n   while (true) {\n      index = (low + high) / 2;\n      if (wordIndex >= lines->getRef(index)->firstWord) {\n         if (wordIndex <= lines->getRef(index)->lastWord)\n            return index;\n         else\n            low = index + 1;\n      } else\n         high = index - 1;\n   }\n}\n\n/**\n * \\brief Find the paragraph of word \\em wordIndex.\n */\nint Textblock::findParagraphOfWord (int wordIndex)\n{\n   int high = paragraphs->size () - 1, index, low = 0;\n\n   if (wordIndex < 0 || wordIndex >= words->size () ||\n       // It may be that the paragraphs list is incomplete. But look\n       // also at fillParagraphs, where this method is called.\n       (paragraphs->size () == 0 ||\n        wordIndex > paragraphs->getLastRef()->lastWord))\n      return -1;\n\n   while (true) {\n      index = (low + high) / 2;\n      if (wordIndex >= paragraphs->getRef(index)->firstWord) {\n         if (wordIndex <= paragraphs->getRef(index)->lastWord)\n            return index;\n         else\n            low = index + 1;\n      } else\n         high = index - 1;\n   }\n}\n\n/**\n * \\brief Find the index of the word, or -1.\n */\nTextblock::Word *Textblock::findWord (int x, int y, bool *inSpace)\n{\n   int lineIndex, wordIndex;\n   int xCursor, lastXCursor, yWidgetBase;\n   Line *line;\n   Word *word;\n\n   *inSpace = false;\n\n   if ((lineIndex = findLineIndexWhenAllocated (y)) >= lines->size ())\n      return NULL;\n   line = lines->getRef (lineIndex);\n   yWidgetBase = lineYOffsetWidget (line) + line->borderAscent;\n   if (yWidgetBase + line->borderDescent <= y)\n      return NULL;\n\n   xCursor = line->textOffset;\n   for (wordIndex = line->firstWord; wordIndex <= line->lastWord;wordIndex++) {\n      word = words->getRef (wordIndex);\n      lastXCursor = xCursor;\n      xCursor += word->size.width + word->effSpace;\n      if (lastXCursor <= x && xCursor > x) {\n         if (x >= xCursor - word->effSpace) {\n            if (wordIndex < line->lastWord &&\n                (words->getRef(wordIndex + 1)->content.type !=\n                 core::Content::BREAK) &&\n                y > yWidgetBase - word->spaceStyle->font->ascent &&\n                y <= yWidgetBase + word->spaceStyle->font->descent) {\n               *inSpace = true;\n               return word;\n            }\n         } else {\n            if (y > yWidgetBase - word->size.ascent &&\n                y <= yWidgetBase + word->size.descent)\n               return word;\n         }\n         break;\n      }\n   }\n\n   return NULL;\n}\n\nvoid Textblock::drawLevel (core::View *view, core::Rectangle *area,\n                           int level, core::DrawingContext *context)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"Textblock::drawLevel\", \"(%d, %d, %d * %d), %s\",\n                  area->x, area->y, area->width, area->height,\n                  stackingLevelText (level));\n\n   switch (level) {\n   case SL_IN_FLOW:\n      for (int lineIndex = findLineIndexWhenAllocated (area->y);\n           lineIndex < lines->size (); lineIndex++) {\n         Line *line = lines->getRef (lineIndex);\n         if (lineYOffsetWidget (line) >= area->y + area->height)\n            break;\n\n         DBG_OBJ_MSGF (\"draw\", 0, \"line %d (of %d)\", lineIndex, lines->size ());\n         drawLine (line, view, area, context);\n      }\n      break;\n\n   case SL_OOF_REF:\n      // TODO Inefficient. Perhaps store OOF references in seperate\n      // (much smaller!) list.\n      for (int oofmIndex = 0; oofmIndex < NUM_OOFM; oofmIndex++) {\n         for (int wordIndex = 0; wordIndex < words->size (); wordIndex++) {\n            Word *word = words->getRef (wordIndex);\n            if (word->content.type == core::Content::WIDGET_OOF_REF &&\n                getOOFMIndex (word->content.widgetReference->widget)\n                == oofmIndex &&\n                doesWidgetOOFInterruptDrawing (word->content.widget))\n               word->content.widget->drawInterruption (view, area, context);\n         }\n      }\n      break;\n\n   default:\n      OOFAwareWidget::drawLevel (view, area, level, context);\n      break;\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Add a new word (text, widget etc.) to a page.\n */\nTextblock::Word *Textblock::addWord (int width, int ascent, int descent,\n                                     short flags, core::style::Style *style)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addWord\", \"%d * (%d + %d), %d, %p\",\n                  width, ascent, descent, flags, style);\n\n   if (lineBreakWidth == -1) {\n      lineBreakWidth = getAvailWidth (true);\n      DBG_OBJ_SET_NUM (\"lineBreakWidth\", lineBreakWidth);\n   }\n\n   words->increase ();\n   DBG_OBJ_SET_NUM (\"words.size\", words->size ());\n   int wordNo = words->size () - 1;\n   initWord (wordNo);\n   fillWord (wordNo, width, ascent, descent, flags, style);\n\n   DBG_OBJ_LEAVE ();\n   return words->getRef (wordNo);\n}\n\n/**\n * Basic initialization, which is neccessary before fillWord.\n */\nvoid Textblock::initWord (int wordNo)\n{\n   Word *word = words->getRef (wordNo);\n\n   word->style = word->spaceStyle = NULL;\n   word->wordImgRenderer = NULL;\n   word->spaceImgRenderer = NULL;\n}\n\nvoid Textblock::cleanupWord (int wordNo)\n{\n   Word *word = words->getRef (wordNo);\n\n   if (word->content.type == core::Content::WIDGET_IN_FLOW)\n      delete word->content.widget;\n   if (word->content.type == core::Content::WIDGET_OOF_REF)\n      delete word->content.widgetReference;\n   /** \\todo What about texts? */\n\n   removeWordImgRenderer (wordNo);\n   removeSpaceImgRenderer (wordNo);\n\n   word->style->unref ();\n   word->spaceStyle->unref ();\n}\n\nvoid Textblock::removeWordImgRenderer (int wordNo)\n{\n   Word *word = words->getRef (wordNo);\n\n   if (word->style && word->wordImgRenderer) {\n      word->style->backgroundImage->removeExternalImgRenderer\n         (word->wordImgRenderer);\n      delete word->wordImgRenderer;\n      word->wordImgRenderer = NULL;\n   }\n}\n\nvoid Textblock::setWordImgRenderer (int wordNo)\n{\n   Word *word = words->getRef (wordNo);\n\n   if (word->style->backgroundImage) {\n      word->wordImgRenderer = new WordImgRenderer (this, wordNo);\n      word->style->backgroundImage->putExternalImgRenderer\n         (word->wordImgRenderer);\n   } else\n      word->wordImgRenderer = NULL;\n}\n\nvoid Textblock::removeSpaceImgRenderer (int wordNo)\n{\n   Word *word = words->getRef (wordNo);\n\n   if (word->spaceStyle && word->spaceImgRenderer) {\n      word->spaceStyle->backgroundImage->removeExternalImgRenderer\n         (word->spaceImgRenderer);\n      delete word->spaceImgRenderer;\n      word->spaceImgRenderer = NULL;\n   }\n}\n\nvoid Textblock::setSpaceImgRenderer (int wordNo)\n{\n   Word *word = words->getRef (wordNo);\n\n   if (word->spaceStyle->backgroundImage) {\n      word->spaceImgRenderer = new SpaceImgRenderer (this, wordNo);\n      word->spaceStyle->backgroundImage->putExternalImgRenderer\n         (word->spaceImgRenderer);\n   } else\n      word->spaceImgRenderer = NULL;\n}\n\nvoid Textblock::fillWord (int wordNo, int width, int ascent, int descent,\n                          short flags, core::style::Style *style)\n{\n   Word *word = words->getRef (wordNo);\n\n   word->size.width = width;\n   word->size.ascent = ascent;\n   word->size.descent = descent;\n   DBG_SET_WORD_SIZE (wordNo);\n   word->origSpace = word->effSpace = 0;\n   word->hyphenWidth = 0;\n   word->badnessAndPenalty.setPenalty (PENALTY_PROHIBIT_BREAK);\n   word->content.space = false;\n   word->flags = flags;\n\n   removeWordImgRenderer (wordNo);\n   removeSpaceImgRenderer (wordNo);\n\n   word->style = style;\n   word->spaceStyle = style;\n\n   setWordImgRenderer (wordNo);\n   setSpaceImgRenderer (wordNo);\n\n   style->ref ();\n   style->ref ();\n}\n\n/*\n * Get the width of a string of text.\n *\n * For \"isStart\" and \"isEnd\" see drawText.\n */\nint Textblock::textWidth(const char *text, int start, int len,\n                         core::style::Style *style, bool isStart, bool isEnd)\n{\n   int ret = 0;\n\n   if (len > 0) {\n      char *str = NULL;\n\n      switch (style->textTransform) {\n         case core::style::TEXT_TRANSFORM_NONE:\n         default:\n            ret = layout->textWidth(style->font, text+start, len);\n            break;\n         case core::style::TEXT_TRANSFORM_UPPERCASE:\n            str = layout->textToUpper(text+start, len);\n            ret = layout->textWidth(style->font, str, strlen(str));\n            break;\n         case core::style::TEXT_TRANSFORM_LOWERCASE:\n            str = layout->textToLower(text+start, len);\n            ret = layout->textWidth(style->font, str, strlen(str));\n            break;\n         case core::style::TEXT_TRANSFORM_CAPITALIZE:\n            if (isStart) {\n               /* \\bug No way to know about non-ASCII punctuation. */\n               bool initial_seen = false;\n\n               for (int i = 0; i < start; i++)\n                  if (!ispunct(text[i]))\n                     initial_seen = true;\n               if (initial_seen) {\n                  ret = layout->textWidth(style->font, text+start, len);\n               } else {\n                  int after = 0;\n\n                  text += start;\n                  while (ispunct(text[after]))\n                     after++;\n                  if (text[after])\n                     after = layout->nextGlyph(text, after);\n                  if (after > len)\n                     after = len;\n                  str = layout->textToUpper(text, after);\n                  ret = layout->textWidth(style->font, str, strlen(str)) +\n                     layout->textWidth(style->font, text+after, len-after);\n               }\n            } else\n               ret = layout->textWidth(style->font, text+start, len);\n            break;\n      }\n\n      if (str)\n         free(str);\n   }\n\n   return ret;\n}\n\n/**\n * Calculate the size of a text word.\n *\n * For \"isStart\" and \"isEnd\" see textWidth and drawText.\n */\nvoid Textblock::calcTextSize (const char *text, size_t len,\n                              core::style::Style *style,\n                              core::Requisition *size, bool isStart, bool isEnd)\n{\n   size->width = textWidth (text, 0, len, style, isStart, isEnd);\n   size->ascent = style->font->ascent;\n   size->descent = style->font->descent;\n\n   /*\n    * For 'normal' line height, just use ascent and descent from font.\n    * For absolute/percentage, line height is relative to font size, which\n    * is (irritatingly) smaller than ascent+descent.\n    */\n   if (style->lineHeight != core::style::LENGTH_AUTO) {\n      int height, leading;\n      float factor = style->font->size;\n\n      factor /= (style->font->ascent + style->font->descent);\n\n      size->ascent = lout::misc::roundInt(size->ascent * factor);\n      size->descent = lout::misc::roundInt(size->descent * factor);\n\n      /* TODO: The containing block's line-height property gives a minimum\n       * height for the line boxes. (Even when it's set to 'normal', i.e.,\n       * AUTO? Apparently.) Once all block elements make Textblocks or\n       * something, this can be handled.\n       */\n      if (core::style::isAbsLength (style->lineHeight))\n         height = core::style::absLengthVal(style->lineHeight);\n      else\n         height =\n            core::style::multiplyWithPerLengthRounded (style->font->size,\n                                                       style->lineHeight);\n      leading = height - style->font->size;\n\n      size->ascent += leading / 2;\n      size->descent += leading - (leading / 2);\n   }\n\n   /* In case of a sub or super script we increase the word's height and\n    * potentially the line's height.\n    */\n   if (style->valign == core::style::VALIGN_SUB) {\n      int requiredDescent = style->font->descent + style->font->ascent / 3;\n      size->descent = misc::max (size->descent, requiredDescent);\n   } else if (style->valign == core::style::VALIGN_SUPER) {\n      int requiredAscent = style->font->ascent + style->font->ascent / 2;\n      size->ascent = misc::max (size->ascent, requiredAscent);\n   }\n}\n\n/**\n * Add a word to the page structure. If it contains dividing\n * characters (hard or soft hyphens, em-dashes, etc.), it is divided.\n */\nvoid Textblock::addText (const char *text, size_t len,\n                         core::style::Style *style)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addText\", \"..., %d, %p\",\n                  (int)len, style);\n\n   // Count dividing characters.\n   int numParts = 1;\n   int utf8_char_count = 0;\n\n   for (const char *s = text; s; s = nextUtf8Char (s, text + len - s), utf8_char_count++) {\n      int foundDiv = -1;\n      for (int j = 0; foundDiv == -1 && j < NUM_DIV_CHARS; j++) {\n         int lDiv = strlen (divChars[j].s);\n         if (s  <= text + len - lDiv) {\n            if (memcmp (s, divChars[j].s, lDiv * sizeof (char)) == 0)\n               foundDiv = j;\n         }\n      }\n\n      if (foundDiv == -1 && utf8_char_count > MAX_UNBROKEN_WORD_LENGTH) {\n         numParts ++;\n         utf8_char_count = 0;\n      }\n\n      if (foundDiv != -1) {\n         if (divChars[foundDiv].penaltyIndexLeft != -1)\n            numParts ++;\n         if (divChars[foundDiv].penaltyIndexRight != -1)\n            numParts ++;\n      }\n   }\n\n   if (numParts == 1) {\n      // Simple (and common) case: no dividing characters. May still\n      // be hyphenated automatically.\n      core::Requisition size;\n      calcTextSize (text, len, style, &size, true, true);\n      addText0 (text, len,\n                Word::CAN_BE_HYPHENATED | Word::WORD_START | Word::WORD_END,\n                style, &size);\n\n      //printf (\"[%p] %d: added simple word: \", this, words->size() - 1);\n      //printWordWithFlags (words->getLastRef());\n      //printf (\"\\n\");\n   } else {\n      PRINTF (\"HYPHENATION: '\");\n      for (size_t i = 0; i < len; i++)\n         PUTCHAR(text[i]);\n      PRINTF (\"', with %d parts\\n\", numParts);\n\n      // Store hyphen positions.\n      int n = 0, totalLenCharRemoved = 0;\n      int partPenaltyIndex[numParts - 1];\n      int partStart[numParts], partEnd[numParts];\n      bool charRemoved[numParts - 1], canBeHyphenated[numParts + 1];\n      bool permDivChar[numParts - 1], unbreakableForMinWidth[numParts - 1];\n      canBeHyphenated[0] = canBeHyphenated[numParts] = true;\n      partStart[0] = 0;\n      partEnd[numParts - 1] = len;\n\n      utf8_char_count = 0;\n      for (const char *s = text; s; s = nextUtf8Char (s, text + len - s), utf8_char_count++) {\n         int foundDiv = -1;\n         for (int j = 0; foundDiv == -1 && j < NUM_DIV_CHARS; j++) {\n            int lDiv = strlen (divChars[j].s);\n            if (s <= text + len - lDiv) {\n               if (memcmp (s, divChars[j].s, lDiv * sizeof (char)) == 0)\n                  foundDiv = j;\n            }\n         }\n\n         if (foundDiv == -1 && utf8_char_count > MAX_UNBROKEN_WORD_LENGTH) {\n            utf8_char_count = 0;\n\t    partPenaltyIndex[n] = PENALTY_HYPHEN;\n\t    charRemoved[n] = false;\n\t    permDivChar[n] = false;\n\t    unbreakableForMinWidth[n] = false;\n\t    canBeHyphenated[n + 1] = false;\n\t    partEnd[n] = s - text;\n\t    partStart[n + 1] = s - text;\n\t    n++;\n         }\n         \n         if (foundDiv != -1) {\n            int lDiv = strlen (divChars[foundDiv].s);\n\n            if (divChars[foundDiv].charRemoved) {\n               assert (divChars[foundDiv].penaltyIndexLeft != -1);\n               assert (divChars[foundDiv].penaltyIndexRight == -1);\n\n               partPenaltyIndex[n] = divChars[foundDiv].penaltyIndexLeft;\n               charRemoved[n] = true;\n               permDivChar[n] = false;\n               unbreakableForMinWidth[n] =\n                  divChars[foundDiv].unbreakableForMinWidth;\n               canBeHyphenated[n + 1] = divChars[foundDiv].canBeHyphenated;\n               partEnd[n] = s - text;\n               partStart[n + 1] = s - text + lDiv;\n               n++;\n               totalLenCharRemoved += lDiv;\n            } else {\n               assert (divChars[foundDiv].penaltyIndexLeft != -1 ||\n                       divChars[foundDiv].penaltyIndexRight != -1);\n\n               if (divChars[foundDiv].penaltyIndexLeft != -1) {\n                  partPenaltyIndex[n] = divChars[foundDiv].penaltyIndexLeft;\n                  charRemoved[n] = false;\n                  permDivChar[n] = false;\n                  unbreakableForMinWidth[n] =\n                     divChars[foundDiv].unbreakableForMinWidth;\n                  canBeHyphenated[n + 1] = divChars[foundDiv].canBeHyphenated;\n                  partEnd[n] = s - text;\n                  partStart[n + 1] = s - text;\n                  n++;\n               }\n\n               if (divChars[foundDiv].penaltyIndexRight != -1) {\n                  partPenaltyIndex[n] = divChars[foundDiv].penaltyIndexRight;\n                  charRemoved[n] = false;\n                  permDivChar[n] = true;\n                  unbreakableForMinWidth[n] =\n                     divChars[foundDiv].unbreakableForMinWidth;\n                  canBeHyphenated[n + 1] = divChars[foundDiv].canBeHyphenated;\n                  partEnd[n] = s - text + lDiv;\n                  partStart[n + 1] = s - text + lDiv;\n                  n++;\n               }\n            }\n         }\n      }\n\n      // Get text without removed characters, e. g. hyphens.\n      const char *textWithoutHyphens;\n      char textWithoutHyphensBuf[len - totalLenCharRemoved];\n      int *breakPosWithoutHyphens, breakPosWithoutHyphensBuf[numParts - 1];\n\n      if (totalLenCharRemoved == 0) {\n         // No removed characters: take original arrays.\n         textWithoutHyphens = text;\n         // Ends are also break positions, except the last end, which\n         // is superfluous, but does not harm (since arrays in C/C++\n         // does not have an implicit length).\n         breakPosWithoutHyphens = partEnd;\n      } else {\n         // Copy into special buffers.\n         textWithoutHyphens = textWithoutHyphensBuf;\n         breakPosWithoutHyphens = breakPosWithoutHyphensBuf;\n\n         int n = 0;\n         for (int i = 0; i < numParts; i++) {\n            memmove (textWithoutHyphensBuf + n, text + partStart[i],\n                     partEnd[i] - partStart[i]);\n            n += partEnd[i] - partStart[i];\n            if (i < numParts - 1)\n               breakPosWithoutHyphensBuf[i] = n;\n         }\n      }\n\n      PRINTF(\"H... without hyphens: '\");\n      for (size_t i = 0; i < len - totalLenCharRemoved; i++)\n         PUTCHAR(textWithoutHyphens[i]);\n      PRINTF(\"'\\n\");\n\n      core::Requisition wordSize[numParts];\n      calcTextSizes (textWithoutHyphens, len - totalLenCharRemoved, style,\n                     numParts - 1, breakPosWithoutHyphens, wordSize);\n\n      // Finished!\n      for (int i = 0; i < numParts; i++) {\n         short flags = 0;\n\n         // If this parts adjoins at least one division characters,\n         // for which canBeHyphenated is set to false (this is the\n         // case for soft hyphens), do not hyphenate.\n         if (canBeHyphenated[i] && canBeHyphenated[i + 1])\n            flags |= Word::CAN_BE_HYPHENATED;\n\n         if(i < numParts - 1) {\n            if (charRemoved[i])\n               flags |= Word::DIV_CHAR_AT_EOL;\n\n            if (permDivChar[i])\n               flags |= Word::PERM_DIV_CHAR;\n            if (unbreakableForMinWidth[i])\n               flags |= Word::UNBREAKABLE_FOR_MIN_WIDTH;\n\n            flags |= Word::DRAW_AS_ONE_TEXT;\n         }\n\n         if (i == 0)\n            flags |= Word::WORD_START;\n         if (i == numParts - 1)\n            flags |= Word::WORD_END;\n\n         addText0 (text + partStart[i], partEnd[i] - partStart[i],\n                   flags, style, &wordSize[i]);\n\n         //printf (\"[%p] %d: added word part: \", this, words->size() - 1);\n         //printWordWithFlags (words->getLastRef());\n         //printf (\"\\n\");\n\n         //PRINTF(\"H... [%d] '\", i);\n         //for (int j = partStart[i]; j < partEnd[i]; j++)\n         //   PUTCHAR(text[j]);\n         //PRINTF(\"' added\\n\");\n\n         if(i < numParts - 1) {\n            Word *word = words->getLastRef();\n\n            setBreakOption (word, style, penalties[partPenaltyIndex[i]][0],\n                            penalties[partPenaltyIndex[i]][1], false);\n            DBG_SET_WORD (words->size () - 1);\n\n            if (charRemoved[i])\n               // Currently, only unconditional hyphens (UTF-8:\n               // \"\\xe2\\x80\\x90\") can be used. See also drawWord, last\n               // section \"if (drawHyphen)\".\n               // Could be extended by adding respective members to\n               // DivChar and Word.\n               word->hyphenWidth =\n                  layout->textWidth (word->style->font, hyphenDrawChar,\n                                     strlen (hyphenDrawChar));\n\n            accumulateWordData (words->size() - 1);\n            correctLastWordExtremes ();\n         }\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::calcTextSizes (const char *text, size_t textLen,\n                               core::style::Style *style,\n                               int numBreaks, int *breakPos,\n                               core::Requisition *wordSize)\n{\n   // The size of the last part is calculated in a simple way.\n   int lastStart = breakPos[numBreaks - 1];\n   calcTextSize (text + lastStart, textLen - lastStart, style,\n                 &wordSize[numBreaks], true, true);\n\n   PRINTF(\"H... [%d] '\", numBreaks);\n   for (size_t i = 0; i < textLen - lastStart; i++)\n      PUTCHAR(text[i + lastStart]);\n   PRINTF(\"' -> %d\\n\", wordSize[numBreaks].width);\n\n   // The rest is more complicated. See dw-line-breaking, section\n   // \"Hyphens\".\n   for (int i = numBreaks - 1; i >= 0; i--) {\n      int start = (i == 0) ? 0 : breakPos[i - 1];\n      calcTextSize (text + start, textLen - start, style, &wordSize[i],\n                    i == 0, i == numBreaks - 1);\n\n      PRINTF(\"H... [%d] '\", i);\n      for (size_t j = 0; j < textLen - start; j++)\n         PUTCHAR(text[j + start]);\n      PRINTF(\"' -> %d\\n\", wordSize[i].width);\n\n      for (int j = i + 1; j < numBreaks + 1; j++) {\n         wordSize[i].width -= wordSize[j].width;\n         PRINTF(\"H...    - %d = %d\\n\", wordSize[j].width, wordSize[i].width);\n      }\n   }\n}\n\n/**\n * Calculate the size of a widget, and return whether it has to be\n * positioned at the top of the line.\n */\nbool Textblock::calcSizeOfWidgetInFlow (int wordIndex, Widget *widget,\n                                        core::Requisition *size)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcSizeOfWidgetInFlow\", \"%d, %p, ...\",\n                  wordIndex, widget);\n\n   bool result, firstWordOfLine;\n\n   if (hasListitemValue)\n      // For list items, the word #0 at the beginning (bullet, number ...) has\n      // to be considered;\n      firstWordOfLine = wordIndex == 1 ||\n         (wordIndex > 0 &&\n          words->getRef(wordIndex - 1)->content.type == core::Content::BREAK);\n   else\n      firstWordOfLine = wordIndex == 0 ||\n         words->getRef(wordIndex - 1)->content.type == core::Content::BREAK;\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"firstWordOfLine = %s\",\n                 boolToStr (firstWordOfLine));\n\n   if (firstWordOfLine &&\n       (widget->getStyle()->display == core::style::DISPLAY_BLOCK ||\n        widget->getStyle()->display == core::style::DISPLAY_LIST_ITEM ||\n        widget->getStyle()->display == core::style::DISPLAY_TABLE)) {\n      // pass positions\n      DBG_OBJ_MSG (\"resize\", 1, \"pass position\");\n      int lastWord = lines->empty () ? -1 : lines->getLastRef()->lastWord;\n      assert (wordIndex > lastWord);\n\n      // The position passed to sizeRequest must be equivalent to the position\n      // passed later to sizeAllocate. This is more complicated for widgets\n      // which are centered or aligned to the right: here, we have to know the\n      // width to calculate the horizontal position. Both are calculated in an\n      // iterative way; initially, the left border is used (like for other,\n      // simpler alignments).\n      \n      // Since the child widget will regard floats, we do not have to include\n      // floats when calculating left and right border.\n\n      // TODO Actually line1OffsetEff should be used instead of line1Offset, but\n      // it may not initialized here. Anyway, since ignoreLine1OffsetSometimes\n      // is not used, line1OffsetEff is always equal to line1Offset.\n      \n      int leftBorder = boxOffsetX () + leftInnerPadding\n         + (lines->size () == 0 ? line1Offset /* ...Eff, actually */ : 0);\n      int rightBorder = boxRestWidth ();\n      \n      int lastMargin, yLine = yOffsetOfLineToBeCreated (&lastMargin);\n      int yRel = yLine - min (lastMargin, widget->getStyle()->margin.top);\n\n      DBG_OBJ_MSGF (\"resize\", 1,\n                    \"leftBorder = %d + %d + (%d == 0 ? %d : 0) = %d, \"\n                    \"rightBorder = %d, yRel = %d - min (%d, %d) = %d\",\n                    boxOffsetX (), leftInnerPadding , lines->size (),\n                    line1OffsetEff, leftBorder, rightBorder, yLine, lastMargin,\n                    widget->getStyle()->margin.top, yRel);\n            \n      core::SizeParams childParams;\n      DBG_OBJ_ASSOC_CHILD (&childParams);\n\n      int oldXRel = leftBorder;\n\n      sizeRequestParams.forChild (this, widget, oldXRel, yRel, &childParams);\n      widget->sizeRequest (size, childParams.getNumPos (),\n                           childParams.getReferences (), childParams.getX (),\n                           childParams.getY ());\n\n      DBG_OBJ_MSG_START ();\n      \n      while (true) {\n         DBG_OBJ_MSG_START ();\n         \n         int xRel;\n\n         switch(widget->getStyle()->textAlign) {\n         case core::style::TEXT_ALIGN_LEFT:\n         case core::style::TEXT_ALIGN_STRING: // see comment in alignLine()\n         case core::style::TEXT_ALIGN_JUSTIFY: // equivalent for only one word\n         default: // compiler happiness\n            xRel = leftBorder;\n            break;\n            \n         case core::style::TEXT_ALIGN_RIGHT:\n            xRel = lineBreakWidth - rightBorder - size->width;\n            break;\n            \n         case core::style::TEXT_ALIGN_CENTER:\n            xRel =\n               (leftBorder + lineBreakWidth - rightBorder - size->width) / 2;\n            break;\n         }\n         \n         // Cf. Textblock::calcTextOffset().\n         if (xRel < leftBorder)\n            xRel = leftBorder;\n      \n         DBG_OBJ_MSGF (\"resize\", 2, \"xRel = %d, oldXRel = %d\", xRel, oldXRel);\n\n         // Stop when the value of xRel has not changed during the last\n         // iteration.\n         if (xRel == oldXRel)\n            break;\n         \n         sizeRequestParams.forChild (this, widget, xRel, yRel, &childParams);\n         widget->sizeRequest (size, childParams.getNumPos (),\n                              childParams.getReferences (), childParams.getX (),\n                              childParams.getY ());\n\n         oldXRel = xRel;\n\n         DBG_OBJ_MSG_END ();\n      }\n\n      DBG_OBJ_MSG_END ();\n      \n      result = true;\n   } else {\n      // do not pass positions (inline elements etc)\n      DBG_OBJ_MSG (\"resize\", 1, \"do not pass position\");\n      widget->sizeRequest (size);\n      result = false;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr (result));\n   return result;\n}\n\nbool Textblock::findSizeRequestReference (Widget *reference, int *xRef,\n                                          int *yRef)\n{\n   if (reference == this) {\n      if (xRef)\n         *xRef = 0;\n      if (yRef)\n         *yRef = 0;\n      return true;\n   } else\n      return sizeRequestParams.findReference (reference, xRef, yRef);\n}\n   \n/**\n * Add a word (without hyphens) to the page structure.\n */\nvoid Textblock::addText0 (const char *text, size_t len, short flags,\n                          core::style::Style *style, core::Requisition *size)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addText0\",\n                  \"..., %d, %s:%s:%s:%s:%s:%s:%s:%s, %p, %d * (%d + %d)\",\n                  (int)len,\n                  (flags & Word::CAN_BE_HYPHENATED) ? \"h?\" : \"--\",\n                  (flags & Word::DIV_CHAR_AT_EOL) ? \"de\" : \"--\",\n                  (flags & Word::PERM_DIV_CHAR) ? \"dp\" : \"--\",\n                  (flags & Word::DRAW_AS_ONE_TEXT) ? \"t1\" : \"--\",\n                  (flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) ? \"um\" : \"--\",\n                  (flags & Word::WORD_START) ? \"st\" : \"--\",\n                  (flags & Word::WORD_END) ? \"en\" : \"--\",\n                  (flags & Word::TOPLEFT_OF_LINE) ? \"00\" : \"--\",\n                  style, size->width, size->ascent, size->descent);\n\n   //printf(\"[%p] addText0 ('\", this);\n   //for (size_t i = 0; i < len; i++)\n   //   putchar(text[i]);\n   //printf(\"', \");\n   //printWordFlags (flags);\n   //printf (\", ...)\\n\");\n\n   Word *word = addWord (size->width, size->ascent, size->descent,\n                         flags, style);\n   DBG_OBJ_ASSOC_CHILD (style);\n   word->content.type = core::Content::TEXT;\n   word->content.text = layout->textZone->strndup(text, len);\n\n   DBG_SET_WORD (words->size () - 1);\n\n   // The following debug message may be useful to identify the\n   // different textblocks.\n\n   //if (words->size() == 1)\n   //   printf (\"[%p] first word: '%s'\\n\", this, text);\n\n   processWord (words->size () - 1);\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Add a widget (word type) to the page.\n */\nvoid Textblock::addWidget (core::Widget *widget, core::style::Style *style)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addWidget\", \"%p, %p\", widget, style);\n\n   /* We first assign -1 as parent_ref, since the call of widget->size_request\n    * will otherwise let this Textblock be rewrapped from the beginning.\n    * (parent_ref is actually undefined, but likely has the value 0.) At the,\n    * end of this function, the correct value is assigned. */\n   widget->parentRef = -1;\n   DBG_OBJ_SET_NUM_O (widget, \"parentRef\", widget->parentRef);\n\n   widget->setStyle (style);\n\n   initOutOfFlowMgrs ();\n\n   if (testWidgetOutOfFlow (widget)) {\n      int oofmIndex = getOOFMIndex (widget);\n      DBG_OBJ_MSGF (\"construct.word\", 1, \"ouf of flow: oofmIndex = %d (%s)\",\n                    oofmIndex, OOFM_NAME[oofmIndex]);\n\n      widget->setParent (oofContainer[oofmIndex]);\n      widget->setGenerator (this);\n\n      oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex);\n      int oofmSubRef = oofm->addWidgetOOF (widget, this, words->size ());\n      widget->parentRef = makeParentRefOOF (oofmIndex, oofmSubRef);\n\n      DBG_OBJ_MSGF (\"construct.word\", 1, \"oofmSubRef = %d => parentRef = %d\",\n                    oofmSubRef, widget->parentRef);\n\n      core::Requisition size;\n      oofm->calcWidgetRefSize (widget, &size);\n      Word *word = addWord (size.width, size.ascent, size.descent, 0, style);\n      word->content.type = core::Content::WIDGET_OOF_REF;\n      word->content.widgetReference = new core::WidgetReference (widget);\n      widget->setWidgetReference (word->content.widgetReference);\n\n      // After a out-of-flow reference, breaking is allowed. (This avoids some\n      // problems with breaking near float definitions.)\n      setBreakOption (word, style, 0, 0, false);\n   } else {\n      DBG_OBJ_MSG (\"construct.word\", 1, \"in flow\");\n\n      widget->setParent (this);\n\n      // TODO Replace (perhaps) later \"textblock\" by \"OOF aware widget\".\n      if (widget->instanceOf (Textblock::CLASS_ID)) {\n         for (int i = 0; i < NUM_OOFM; i++)\n            searchOutOfFlowMgr(i)->addWidgetInFlow ((Textblock*)widget, this,\n                                                    words->size ());\n      }\n\n      core::Requisition size;\n      short flags = calcSizeOfWidgetInFlow (words->size (), widget, &size) ?\n         Word::TOPLEFT_OF_LINE : 0;\n      Word *word =\n         addWord (size.width, size.ascent, size.descent, flags, style);\n      word->content.type = core::Content::WIDGET_IN_FLOW;\n      word->content.widget = widget;\n   }\n\n   DBG_SET_WORD (words->size () - 1);\n\n   processWord (words->size () - 1);\n   //DBG_OBJ_SET_NUM (word->content.widget, \"parent_ref\",\n   //                 word->content.widget->parent_ref);\n\n   //DEBUG_MSG(DEBUG_REWRAP_LEVEL,\n   //          \"Assigning parent_ref = %d to added word %d, \"\n   //          \"in page with %d word(s)\\n\",\n   //          lines->size () - 1, words->size() - 1, words->size());\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Add an anchor to the page. \"name\" is copied, so no strdup is necessary for\n * the caller.\n *\n * Return true on success, and false, when this anchor had already been\n * added to the widget tree.\n */\nbool Textblock::addAnchor (const char *name, core::style::Style *style)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addAnchor\", \"\\\"%s\\\", %p\", name, style);\n\n   char *copy;\n   int y;\n   bool result;\n\n   // Since an anchor does not take any space, it is safe to call\n   // addAnchor already here.\n   if (wasAllocated ()) {\n      if (lines->size () == 0)\n         y = allocation.y;\n      else\n         y = allocation.y + lineYOffsetWidget (lines->size () - 1);\n      copy = Widget::addAnchor (name, y);\n   } else\n      copy = Widget::addAnchor (name);\n\n   if (copy == NULL)\n      /**\n       * \\todo It may be necessary for future uses to save the anchor in\n       *    some way, e.g. when parts of the widget tree change.\n       */\n      result = false;\n   else {\n      Anchor *anchor;\n\n      anchors->increase();\n      anchor = anchors->getRef(anchors->size() - 1);\n      anchor->name = copy;\n      anchor->wordIndex = words->size();\n      result =  true;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr(result));\n   return result;\n}\n\n\n/**\n * ?\n */\nvoid Textblock::addSpace (core::style::Style *style)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addSpace\", \"%p\", style);\n\n   int wordIndex = words->size () - 1;\n   if (wordIndex >= 0) {\n      fillSpace (wordIndex, style);\n      DBG_SET_WORD (wordIndex);\n      accumulateWordData (wordIndex);\n      correctLastWordExtremes ();\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Add a break option (see setBreakOption() for details). Used instead\n * of addSpace for ideographic characters.\n *\n * When \"forceBreak\" is true, a break is even possible within PRE etc.\n */\nvoid Textblock::addBreakOption (core::style::Style *style, bool forceBreak)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addBreakOption\", \"%p, %s\",\n                  style, forceBreak ? \"true\" : \"false\");\n\n   int wordIndex = words->size () - 1;\n   if (wordIndex >= 0) {\n      setBreakOption (words->getRef(wordIndex), style, 0, 0, forceBreak);\n      DBG_SET_WORD (wordIndex);\n      // Call of accumulateWordData() is not needed here.\n      correctLastWordExtremes ();\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::fillSpace (int wordNo, core::style::Style *style)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"fillSpace\", \"%d, ...\", wordNo);\n\n   DBG_OBJ_MSGF (\"construct.word\", 1, \"style.white-space = %s\",\n                 style->whiteSpace == core::style::WHITE_SPACE_NORMAL ? \"normal\"\n                 : style->whiteSpace == core::style::WHITE_SPACE_PRE ? \"pre\"\n                 : style->whiteSpace == core::style::WHITE_SPACE_NOWRAP ?\n                 \"nowrap\"\n                 : style->whiteSpace == core::style::WHITE_SPACE_PRE_WRAP ?\n                 \"pre-wrap\"\n                 : style->whiteSpace == core::style::WHITE_SPACE_PRE_LINE ?\n                 \"pre-line\" : \"???\");\n\n   // Old comment:\n   //\n   //     According to\n   //     http://www.w3.org/TR/CSS2/text.html#white-space-model: \"line\n   //     breaking opportunities are determined based on the text\n   //     prior to the white space collapsing steps\".\n   //\n   //     So we call addBreakOption () for each Textblock::addSpace ()\n   //     call.  This is important e.g. to be able to break between\n   //     foo and bar in: <span style=\"white-space:nowrap\">foo </span>\n   //     bar\n   //\n   // TODO: Re-evaluate again.\n\n   Word *word = words->getRef (wordNo);\n\n   // TODO: This line does not work: addBreakOption (word, style);\n\n   if (// Do not override a previously set break penalty:\n       !word->content.space &&\n       // OOF references are considered specially, and must not have a space:\n       word->content.type != core::Content::WIDGET_OOF_REF) {\n      setBreakOption (word, style, 0, 0, false);\n\n      word->content.space = true;\n      word->origSpace = word->effSpace =\n         style->font->spaceWidth + style->wordSpacing;\n\n      removeSpaceImgRenderer (wordNo);\n\n      word->spaceStyle->unref ();\n      word->spaceStyle = style;\n      style->ref ();\n\n      setSpaceImgRenderer (wordNo);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Set a break option, if allowed by the style. Called by fillSpace\n * (and so addSpace), but may be called, via addBreakOption(), as an\n * alternative, e. g. for ideographic characters.\n */\nvoid Textblock::setBreakOption (Word *word, core::style::Style *style,\n                                int breakPenalty1, int breakPenalty2,\n                                bool forceBreak)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"setBreakOption\", \"..., %d, %d, %s\",\n                  breakPenalty1, breakPenalty2, forceBreak ? \"true\" : \"false\");\n\n   // TODO: lineMustBeBroken should be independent of the penalty\n   // index? Otherwise, examine the last line.\n   if (!word->badnessAndPenalty.lineMustBeBroken(0)) {\n      if (forceBreak || isBreakAllowed (style))\n         word->badnessAndPenalty.setPenalties (breakPenalty1, breakPenalty2);\n      else\n         word->badnessAndPenalty.setPenalty (PENALTY_PROHIBIT_BREAK);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nbool Textblock::isBreakAllowed (core::style::Style *style)\n{\n   switch (style->whiteSpace) {\n   case core::style::WHITE_SPACE_NORMAL:\n   case core::style::WHITE_SPACE_PRE_LINE:\n   case core::style::WHITE_SPACE_PRE_WRAP:\n      return true;\n\n   case core::style::WHITE_SPACE_PRE:\n   case core::style::WHITE_SPACE_NOWRAP:\n      return false;\n\n   default:\n      // compiler happiness\n      lout::misc::assertNotReached ();\n      return false;\n   }\n}\n\n\n/**\n * Cause a paragraph break\n */\nvoid Textblock::addParbreak (int space, core::style::Style *style)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addParbreak\", \"%d, %p\",\n                  space, style);\n   DBG_OBJ_MSG (\"construct.word\", 0,\n                \"<i>No nesting! Strack trace may be incomplete.</i>\");\n   DBG_OBJ_LEAVE ();\n\n   Word *word;\n\n   /* A break may not be the first word of a page, or directly after\n      the bullet/number (which is the first word) in a list item. (See\n      also comment in sizeRequest.) */\n   if (words->size () == 0 ||\n       (hasListitemValue && words->size () == 1)) {\n      /* This is a bit hackish: If a break is added as the\n         first/second word of a page, and the parent widget is also a\n         Textblock, and there is a break before -- this is the case when\n         a widget is used as a text box (lists, blockquotes, list\n         items etc) -- then we simply adjust the break before, in a\n         way that the space is in any case visible. */\n      /* Find the widget where to adjust the breakSpace. (Only\n         consider normal flow, no floats etc.) */\n      for (Widget *widget = this;\n           widget->getParent() != NULL &&\n              widget->getParent()->instanceOf (Textblock::CLASS_ID) &&\n              !isWidgetOOF (widget);\n           widget = widget->getParent ()) {\n         Textblock *textblock2 = (Textblock*)widget->getParent ();\n         int index = textblock2->hasListitemValue ? 1 : 0;\n         bool isfirst = (textblock2->words->getRef(index)->content.type\n                         == core::Content::WIDGET_IN_FLOW\n                         && textblock2->words->getRef(index)->content.widget\n                         == widget);\n         if (!isfirst) {\n            /* The text block we searched for has been found. */\n            Word *word2;\n            int lineno = getWidgetInFlowSubRef (widget);\n\n            if (lineno > 0 &&\n                (word2 =\n                 textblock2->words->getRef(textblock2->lines\n                                           ->getRef(lineno - 1)->firstWord)) &&\n                word2->content.type == core::Content::BREAK) {\n               if (word2->content.breakSpace < space) {\n                  word2->content.breakSpace = space;\n                  textblock2->queueResize (makeParentRefInFlow (lineno), false);\n                  textblock2->mustQueueResize = false;\n                  DBG_OBJ_SET_BOOL_O (textblock2, \"mustQueueResize\",\n                                      textblock2->mustQueueResize);\n               }\n            }\n            return;\n         }\n         /* Otherwise continue to examine parents. */\n      }\n\n      /* Return in any case. */\n      return;\n   }\n\n   /* Another break before? */\n   if ((word = words->getRef(words->size () - 1)) &&\n       word->content.type == core::Content::BREAK) {\n      Line *lastLine = lines->getRef (lines->size () - 1);\n\n      word->content.breakSpace =\n         misc::max (word->content.breakSpace, space);\n      lastLine->breakSpace =\n         misc::max (word->content.breakSpace,\n                    lastLine->marginDescent - lastLine->borderDescent,\n                    lastLine->breakSpace);\n      return;\n   }\n\n   word = addWord (0, 0, 0, 0, style);\n   DBG_OBJ_ASSOC_CHILD (style);\n   word->content.type = core::Content::BREAK;\n   word->badnessAndPenalty.setPenalty (PENALTY_FORCE_BREAK);\n   word->content.breakSpace = space;\n\n   DBG_SET_WORD (words->size () - 1);\n\n   breakAdded ();\n   processWord (words->size () - 1);\n}\n\n/*\n * Cause a line break.\n */\nvoid Textblock::addLinebreak (core::style::Style *style)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"addLinebreak\", \"%p\", style);\n\n   Word *word;\n\n   if (words->size () == 0 ||\n       words->getRef(words->size () - 1)->content.type == core::Content::BREAK)\n      // An <BR> in an empty line gets the height of the current font\n      // (why would someone else place it here?), ...\n      word =\n         addWord (0, style->font->ascent, style->font->descent, 0, style);\n   else\n      // ... otherwise, it has no size (and does not enlarge the line).\n      word = addWord (0, 0, 0, 0, style);\n\n   DBG_OBJ_ASSOC_CHILD (style);\n\n   word->content.type = core::Content::BREAK;\n   word->badnessAndPenalty.setPenalty (PENALTY_FORCE_BREAK);\n   word->content.breakSpace = 0;\n\n   DBG_SET_WORD (words->size () - 1);\n\n   breakAdded ();\n   processWord (words->size () - 1);\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Called directly after a (line or paragraph) break has been added.\n */\nvoid Textblock::breakAdded ()\n{\n   assert (words->size () >= 1);\n   assert (words->getRef(words->size () - 1)->content.type\n           == core::Content::BREAK);\n\n   // Any space before is removed. It is not used; on the other hand,\n   // this snippet (an example from a real-world debugging session)\n   // would cause problems:\n   //\n   // <input style=\"width: 100%\" .../>\n   // <button ...>...</button>\n   //\n   // (Notice the space between <input> and <button>, and also that\n   // the HTML parser will insert a BREAK between them.) The <input>\n   // would be given the available width (\"width: 100%\"), but the\n   // actual width (Word::totalWidth) would include the space, so that\n   // the width of the line is larger than the available width.\n\n   if (words->size () >= 2)\n      words->getRef(words->size () - 2)->origSpace =\n         words->getRef(words->size () - 2)->effSpace = 0;\n}\n\ncore::Widget *Textblock::getWidgetAtPointLevel (int x, int y, int level,\n                                               core::GettingWidgetAtPointContext\n                                                *context)\n{\n   DBG_OBJ_ENTER (\"events\", 0, \"Textblock::getWidgetAtPointLevel\", \"%d, %d, %s\",\n                  x, y, stackingLevelText (level));\n\n   Widget *widgetAtPoint = NULL;\n\n   switch (level) {\n   case SL_IN_FLOW:\n      {\n         int lineIndex = findLineIndexWhenAllocated (y - allocation.y);\n      \n         if (lineIndex >= 0 && lineIndex < lines->size ()) {\n            Line *line = lines->getRef (lineIndex);\n\n            for (int wordIndex = line->lastWord;\n                 widgetAtPoint == NULL && wordIndex >= line->firstWord;\n                 wordIndex--) {\n               Word *word =  words->getRef (wordIndex);\n               if (word->content.type == core::Content::WIDGET_IN_FLOW &&\n                   !core::StackingContextMgr::handledByStackingContextMgr\n                      (word->content.widget))\n                   widgetAtPoint =\n                      word->content.widget->getWidgetAtPoint (x, y, context);\n            }\n         }\n      }\n      break;\n\n   case SL_OOF_REF:\n      // TODO Inefficient. Perhaps store OOF references in seperate\n      // (much smaller!) list.\n      for (int oofmIndex = NUM_OOFM; widgetAtPoint == NULL && oofmIndex >= 0;\n           oofmIndex--) {\n         for (int wordIndex = words->size () - 1;\n              widgetAtPoint == NULL && wordIndex >= 0; wordIndex--) {\n            Word *word = words->getRef (wordIndex);\n            if (word->content.type == core::Content::WIDGET_OOF_REF &&\n                getOOFMIndex (word->content.widgetReference->widget)\n                == oofmIndex &&\n                doesWidgetOOFInterruptDrawing (word->content.widgetReference\n                                               ->widget))\n               widgetAtPoint = \n                  word->content.widgetReference->widget\n                  ->getWidgetAtPointInterrupted (x, y, context);\n         }\n      }\n      break;\n\n   default:\n      widgetAtPoint =\n         OOFAwareWidget::getWidgetAtPointLevel (x, y, level, context);\n      break;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%p\", widgetAtPoint);\n   return widgetAtPoint;\n}\n\n/**\n * This function \"hands\" the last break of a page \"over\" to a parent\n * page. This is used for \"collapsing spaces\".\n */\nvoid Textblock::handOverBreak (core::style::Style *style)\n{\n   if (lines->size() > 0) {\n      Widget *parent;\n      Line *lastLine = lines->getRef (lines->size () - 1);\n\n      if (lastLine->breakSpace != 0 && (parent = getParent()) &&\n          parent->instanceOf (Textblock::CLASS_ID) &&\n          parent->getStyle()->display != core::style::DISPLAY_BLOCK) {\n         Textblock *textblock2 = (Textblock*) parent;\n         textblock2->addParbreak(lastLine->breakSpace, style);\n      }\n   }\n}\n\n/*\n * Any words added by addWord() are not immediately (queued to\n * be) drawn, instead, this function must be called. This saves some\n * calls to queueResize().\n *\n */\nvoid Textblock::flush ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"flush\");\n\n   if (mustQueueResize) {\n      DBG_OBJ_MSG (\"resize\", 0, \"mustQueueResize set\");\n\n      queueResize (-1, true);\n      mustQueueResize = false;\n      DBG_OBJ_SET_BOOL (\"mustQueueResize\", mustQueueResize);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n\n// next: Dw_page_find_word\n\nvoid Textblock::changeLinkColor (int link, int newColor)\n{\n   for (int lineIndex = 0; lineIndex < lines->size(); lineIndex++) {\n      bool changed = false;\n      Line *line = lines->getRef (lineIndex);\n      int wordIdx;\n\n      for (wordIdx = line->firstWord; wordIdx <= line->lastWord; wordIdx++){\n         Word *word = words->getRef(wordIdx);\n\n         if (word->style->x_link == link) {\n            core::style::StyleAttrs styleAttrs;\n\n            switch (word->content.type) {\n            case core::Content::TEXT:\n            {  core::style::Style *old_style = word->style;\n               styleAttrs = *old_style;\n               styleAttrs.color = core::style::Color::create (layout,\n                                                              newColor);\n               word->style = core::style::Style::create (&styleAttrs);\n               old_style->unref();\n               old_style = word->spaceStyle;\n               styleAttrs = *old_style;\n               styleAttrs.color = core::style::Color::create (layout,\n                                                              newColor);\n               word->spaceStyle = core::style::Style::create(&styleAttrs);\n               old_style->unref();\n               break;\n            }\n            case core::Content::WIDGET_IN_FLOW:\n            {  core::Widget *widget = word->content.widget;\n               styleAttrs = *widget->getStyle();\n               styleAttrs.color = core::style::Color::create (layout,\n                                                              newColor);\n               styleAttrs.setBorderColor(\n                           core::style::Color::create (layout, newColor));\n               widget->setStyle(core::style::Style::create (&styleAttrs));\n               break;\n            }\n            default:\n               break;\n            }\n            changed = true;\n         }\n      }\n      if (changed)\n         queueDrawArea (0, lineYOffsetWidget(line), allocation.width,\n                        line->borderAscent + line->borderDescent);\n   }\n}\n\nvoid Textblock::changeWordStyle (int from, int to, core::style::Style *style,\n                                 bool includeFirstSpace, bool includeLastSpace)\n{\n}\n\nvoid Textblock::queueDrawRange (int index1, int index2)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"queueDrawRange\", \"%d, %d\", index1, index2);\n\n   int from = misc::min (index1, index2);\n   int to = misc::max (index1, index2);\n\n   from = misc::min (from, words->size () - 1);\n   from = misc::max (from, 0);\n   to = misc::min (to, words->size () - 1);\n   to = misc::max (to, 0);\n\n   int line1idx = findLineOfWord (from);\n   int line2idx = findLineOfWord (to);\n\n   if (line1idx >= 0 && line2idx >= 0) {\n      Line *line1 = lines->getRef (line1idx),\n           *line2 = lines->getRef (line2idx);\n      int y = lineYOffsetWidget (line1) + line1->borderAscent -\n              line1->contentAscent;\n      int h = lineYOffsetWidget (line2) + line2->borderAscent +\n              line2->contentDescent - y;\n\n      queueDrawArea (0, y, allocation.width, h);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::updateReference (int ref)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"updateReference\", \"%d\", ref);\n\n   // Only `queueResize` when there're words or float clearance\n   // (float clearance may change `extraSpace.top`).\n   if (words->size () > 0 || getStyle()->clear != core::style::CLEAR_NONE)\n      queueResize (ref, false);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::widgetRefSizeChanged (int externalIndex)\n{\n   int lineNo = findLineOfWord (externalIndex);\n   if (lineNo >= 0 && lineNo < lines->size ())\n      queueResize (makeParentRefInFlow (lineNo), true);\n}\n\nvoid Textblock::oofSizeChanged (bool extremesChanged)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"oofSizeChanged\", \"%s\",\n                  extremesChanged ? \"true\" : \"false\");\n   queueResize (-1, extremesChanged);\n\n   // See Textblock::getAvailWidthOfChild(): Extremes changes may become also\n   // relevant for the children, under certain conditions:\n   if (extremesChanged && !usesMaxGeneratorWidth ())\n      containerSizeChanged ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nint Textblock::getGeneratorX (int oofmIndex)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"Textblock::getGeneratorX\", \"%d\", oofmIndex);\n\n   int x, xRef;\n   if (findSizeRequestReference (oofmIndex, &xRef, NULL))\n      x = xRef;\n   else {\n      // Only called for floats, so this should not happen:\n      assertNotReached ();\n      x = 0;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", x);\n   return x;\n}\n\nint Textblock::getGeneratorY (int oofmIndex)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"Textblock::getGeneratorY\", \"%d\", oofmIndex);\n\n   int yRef, y;\n   if (findSizeRequestReference (oofmIndex, NULL, &yRef))\n      y = yRef;\n   else {\n      // Only called for floats, so this should not happen:\n      assertNotReached ();\n      y = 0;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", y);\n   return y;\n}\n\nint Textblock::getGeneratorRest (int oofmIndex)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"Textblock::getGeneratorRest\", \"%d\", oofmIndex);\n\n   int xRef, rest;\n   OOFAwareWidget *container = oofContainer[oofmIndex];\n   \n   if (container != NULL && findSizeRequestReference (container, &xRef, NULL))\n      rest = container->getGeneratorWidth () - (xRef + getGeneratorWidth ());\n   else {\n      // Only callend for floats, so this should not happen:\n      assertNotReached ();\n      rest = 0;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", rest);\n   return rest;\n}\n\nint Textblock::getGeneratorWidth ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"Textblock::getGeneratorWidth\");\n   \n   // Cf. sizeRequestImpl.\n   if (usesMaxGeneratorWidth ()) {\n      DBG_OBJ_LEAVE_VAL (\"%d\", lineBreakWidth);\n      return lineBreakWidth;\n   } else {\n      // In some cases (especially when called from sizeRequest for an\n      // ancestor), the value is not up to date, since content from children is\n      // not yet added to lines. Moreover, this leads to inconsistencies between\n      // this widget and ancestors (as in Textblock::getGeneratorRest). For this\n      // reason, the children are examined recursively.\n      //\n      // Test case:\n      //\n      // <div style=\"float:left\">\n      //     <div div style=\"float:right\">float</div>\n      //     <div>abcdefghijkl mnopqrstuvwx</div>\n      // </div>\n                                                                       \n      int wChild = 0;\n      int firstWordAfterLastLine =\n         lines->size() > 0 ? lines->getLastRef()->lastWord + 1 : 0;\n      for (int i = firstWordAfterLastLine; i < words->size(); i++) {\n         Word *word = words->getRef(i);\n         int xRel;\n         // We only examine instances of dw::Textblock, since they are relevant\n         // for floats, for which this method is only called.\n         if(word->content.type == core::Content::WIDGET_IN_FLOW &&\n            word->content.widget->instanceOf(Textblock::CLASS_ID)) {\n            Textblock *tbChild = (Textblock*)word->content.widget;\n            if(tbChild->findSizeRequestReference(this, &xRel, NULL))\n               wChild = max(wChild, xRel + tbChild->getGeneratorWidth());\n         }\n      }               \n\n      DBG_OBJ_MSGF (\"resize\", 1, \"wChild = %d\", wChild);\n          \n      int w0 = lines->size () > 0 ? lines->getLastRef()->maxLineWidth : 0;\n      DBG_OBJ_MSGF (\"resize\", 1, \"w0 = %d\", w0);\n      int wThis = min(w0 + leftInnerPadding + boxDiffWidth (), lineBreakWidth);\n      DBG_OBJ_MSGF (\"resize\", 1, \"wThis = min(%d + %d + %d, %d) = %d\",\n                    w0, leftInnerPadding, boxDiffWidth (), lineBreakWidth,\n                    wThis);\n      int w = max(wThis, wChild);\n      DBG_OBJ_LEAVE_VAL (\"max(%d, %d) = %d\", wThis, wChild, w);\n      return w;\n   }\n}\n\nint Textblock::getMaxGeneratorWidth ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"Textblock::getMaxGeneratorWidth\");\n   DBG_OBJ_LEAVE_VAL (\"%d\", lineBreakWidth);\n   return lineBreakWidth;\n}\n\nbool Textblock::usesMaxGeneratorWidth ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"usesMaxGeneratorWidth\");\n\n   bool result;\n   if (treatAsInline) {\n      DBG_OBJ_MSG (\"resize\", 1, \"treatAsInline set\");\n      result = false;\n   } else {\n      bool toplevel = getParent () == NULL,\n         block = getStyle()->display == core::style::DISPLAY_BLOCK,\n         vloat = testWidgetFloat (this),\n         abspos = testWidgetAbsolutelyPositioned (this),\n         fixpos = testWidgetFixedlyPositioned (this);\n      DBG_OBJ_MSGF(\"resize\", 1,\n                   \"toplevel: %s, block: %s, float: %s, abspos: %s, fixpos: %s\",\n                   boolToStr(toplevel), boolToStr(block), boolToStr(vloat),\n                   boolToStr(abspos), boolToStr(fixpos));\n\n      // In detail, this depends on what the respective OOFM does with the\n      // child widget:\n      result = toplevel || (block && !(vloat || abspos || fixpos));\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr(result));\n   return result;\n}\n\nbool Textblock::isPossibleOOFContainer (int oofmIndex)\n{\n   return true;\n}\n\nbool Textblock::isPossibleOOFContainerParent (int oofmIndex)\n{\n   return true;\n}\n\nRegardingBorder *Textblock::getWidgetRegardingBorderForLine (Line *line)\n{\n   return getWidgetRegardingBorderForLine (line->firstWord, line->lastWord);\n}\n\nRegardingBorder *Textblock::getWidgetRegardingBorderForLine (int lineNo)\n{\n   // Can also be used for a line not yet existing.\n   int firstWord = lineNo == 0 ? 0 : lines->getRef(lineNo - 1)->lastWord + 1;\n   int lastWord = lineNo < lines->size() ?\n      lines->getRef(lineNo)->lastWord : words->size() - 1;\n   return getWidgetRegardingBorderForLine (firstWord, lastWord);\n}\n\nRegardingBorder *Textblock::getWidgetRegardingBorderForLine (int firstWord,\n                                                             int lastWord)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"getWidgetRegardingBorderForLine\", \"%d, %d\",\n                  firstWord, lastWord);\n   DBG_OBJ_MSGF (\"resize\", 1, \"words.size = %d\", words->size ());\n\n   RegardingBorder *widgetRegardingBorder = NULL;\n\n   if (firstWord < words->size ()) {\n      // Any instance of a subclass of WidgetRegardingBorder is always\n      // between two line breaks, and so the first word of the line.\n      Word *word = words->getRef (firstWord);\n\n      DBG_MSG_WORD (\"resize\", 1, \"<i>first word:</i> \", firstWord, \"\");\n\n      if (word->content.type == core::Content::WIDGET_IN_FLOW) {\n         Widget *widget = word->content.widget;\n         if (widget->instanceOf (RegardingBorder::CLASS_ID) &&\n             // Exclude cases where a textblock constitutes a new floats\n             // container.\n             !isOOFContainer (widget, OOFM_FLOATS))\n            widgetRegardingBorder = (RegardingBorder*)widget;\n      }\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%p\", widgetRegardingBorder);\n   return widgetRegardingBorder;\n}\n\n/**\n * Includes margin, border, and padding.\n */\nint Textblock::yOffsetOfLineToBeCreated (int *lastMargin)\n{\n   // This method does not return an exact result: the position of the\n   // new line, which does not yet exist, cannot be calculated, since\n   // the top margin of the new line (which collapses either with the\n   // top margin of the textblock widget, or the bottom margin of the\n   // last line) must be taken into account. However, this method is\n   // only called for positioning floats; here, a slight incorrectness\n   // does not cause real harm.\n\n   // (Similar applies to the line *height*, which calculated in an\n   // iterative way; see wrapWordInFlow. Using the same approach for\n   // the *position* is possible, but not worth the increased\n   // complexity.)\n\n   DBG_OBJ_ENTER0 (\"line.yoffset\", 0, \"yOffsetOfLineToBeCreated\");\n\n   int result;\n\n   if (lines->size () == 0) {\n      result = calcVerticalBorder (getStyle()->padding.top,\n                                   getStyle()->borderWidth.top + extraSpace.top,\n                                   getStyle()->margin.top, 0, 0);\n      if (lastMargin)\n         *lastMargin = getStyle()->margin.top;\n   } else {\n      Line *firstLine = lines->getRef (0), *lastLine = lines->getLastRef ();\n      result = calcVerticalBorder (getStyle()->padding.top,\n                                   getStyle()->borderWidth.top,\n                                   getStyle()->margin.top + extraSpace.top,\n                                   firstLine->borderAscent,\n                                   firstLine->marginAscent)\n         - firstLine->borderAscent + lastLine->top + lastLine->totalHeight (0);\n      if (lastMargin)\n         *lastMargin = lastLine->marginDescent - lastLine->borderDescent;\n   }\n\n   if (lastMargin)\n      DBG_OBJ_LEAVE_VAL (\"%d, %d\", result, *lastMargin);\n   else\n      DBG_OBJ_LEAVE_VAL (\"%d\", result);\n\n   return result;\n}\n\n/**\n * Includes margin, border, and padding. Can be used without allocation.\n */\nint Textblock::yOffsetOfLineCreated (Line *line)\n{\n   // Similar applies (regarding exactness) as to yOffsetOfLineToBeCreated.\n\n   DBG_OBJ_ENTER0 (\"line.yoffset\", 0, \"yOffsetOfLineToBeCreated\");\n\n   int result;\n\n   Line *firstLine = lines->getRef (0);\n   result = calcVerticalBorder (getStyle()->padding.top,\n                                getStyle()->borderWidth.top,\n                                getStyle()->margin.top + extraSpace.top,\n                                firstLine->borderAscent,\n                                firstLine->marginAscent)\n      - firstLine->borderAscent + line->top;\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", result);\n   return result;\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/textblock.hh",
    "content": "#ifndef __DW_TEXTBLOCK_HH__\n#define __DW_TEXTBLOCK_HH__\n\n#include <limits.h>\n\n#include \"regardingborder.hh\"\n#include \"../lout/misc.hh\"\n\n// These were used when improved line breaking and hyphenation were implemented.\n// Should be, bit by bit, replaced by RTFL (see ../lout/debug.hh).\n#define PRINTF(fmt, ...)\n#define PUTCHAR(ch)\n\n#ifdef DBG_RTFL\n#   define DEBUG\n#endif\n\nnamespace dw {\n\n/**\n * \\brief A Widget for rendering text blocks, i.e. paragraphs or sequences\n *    of paragraphs.\n *\n * <div style=\"border: 2px solid #ffff00; margin-top: 0.5em;\n * margin-bottom: 0.5em; padding: 0.5em 1em; background-color:\n * #ffffe0\"><b>Info:</b> Some (not so) recent changes, line breaking\n * and hyphenation, have not yet been incorporated into this\n * documentation. See \\ref dw-line-breaking.</div>\n *\n * <h3>Signals</h3>\n *\n * dw::Textblock uses the signals defined in\n * dw::core::Layout::LinkReceiver, related to links. The coordinates are\n * always -1.\n *\n *\n * <h3>Collapsing Spaces</h3>\n *\n * <div style=\"border: 2px solid #ffff00; margin-top: 0.5em;\n * margin-bottom: 0.5em; padding: 0.5em 1em; background-color:\n * #ffffe0\"><b>Info:</b> Collapsing spaces are deprecated, in favor of\n * collapsing margins (see below).</div>\n *\n * The idea behind this is that every paragraph has a specific vertical\n * space around and that they are combined to one space, according to\n * rules stated below. A paragraph consists either of the lines between\n * two paragraph breaks within a dw::Textblock, or of a dw::Textblock\n * within a dw::Textblock, in a single line; the latter is used for\n * indented boxes and list items.\n *\n * The rules:\n *\n * <ol>\n * <li> If a paragraph is following by another, the space between them is the\n *      maximum of both box spaces:\n *\n *      \\image html dw-textblock-collapsing-spaces-1-1.png\n *\n *      are combined like this:\n *\n *      \\image html dw-textblock-collapsing-spaces-1-2.png\n *\n * <li> a) If one paragraph is the first paragraph within another, the upper\n *      space of these paragraphs collapse. b) The analogue is the case for the\n *      last box:\n *\n *      \\image html dw-textblock-collapsing-spaces-2-1.png\n *\n *      If B and C are put into A, the result is:\n *\n *      \\image html dw-textblock-collapsing-spaces-2-2.png\n * </ol>\n *\n * For achieving this, there are some features of dw::Textblock:\n *\n * <ul>\n * <li> Consequent breaks are automatically combined, according to\n *      rule 1. See the code of dw::Textblock::addParBreak for details.\n *\n * <li> If a break is added as the first word of the dw::Textblock within\n *      another dw::Textblock, collapsing according to rule 2a is done\n *      automatically. See the code of dw::Textblock::addParBreak.\n *\n *  <li> To collapse spaces according to rule 2b,\n *       dw::Textblock::addParBreak::handOverBreak must be called for\n *       the \\em inner widget. The HTML parser does this in\n *       Html_eventually_pop_dw.\n * </ul>\n *\n *\n * <h3>Collapsing Margins</h3>\n *\n * Collapsing margins, as defined in the CSS2 specification, are,\n * supported in addition to collapsing spaces. Also, spaces and\n * margins collapse themselves. I.&nbsp;e., the space between two\n * paragraphs is the maximum of the space calculated as described in\n * \"Collapsing Spaces\" and the space calculated according to the rules\n * for collapsing margins.\n *\n * (This is an intermediate hybrid state, collapsing spaces are used in\n * the current version of dillo, while I implemented collapsing margins\n * for the CSS prototype and integrated it already into the main trunk. For\n * a pure CSS-based dillo, collapsing spaces will not be needed anymore, and\n * may be removed for simplicity.)\n *\n * Currently implemented cases:\n *\n * - The top margin of of the textblock widget and the top margin of\n *   the first line box (based on widgets in the first line) collapse.\n *\n * - The bottom margin of of the textblock widget and the bottom\n *   margin of the last line box collapse.\n *\n * - The bottom margin of a line box and the top margin of the\n *   following line collapse. Here, the break space is regarded, too.\n *\n * Open issues:\n *\n * - Only the value of Style::margin is regarded, not the result of\n *   the collapsing itself. For the widgets A, B (child of A), and C\n *   (child of B), the effective margin of A is the maximum of the\n *   *style* margins of A and B, while the effective margin of B (the\n *   collapsed margin of B and C) is ignored here. This could be\n *   solved by introducing an additional \"effective\" (\"calculated\",\n *   \"collapsed\") margin as an attribute of Widget.\n *\n * - For similar reasons, backgrounds to not work exactly. Usage of\n *   Widget::extraSpace should fix this, but it is only fully working\n *   in the GROWS branch (<http://flpsed.org/hgweb/dillo_grows>).\n *\n *   Update: This needs to be re-evaluated.\n *\n * - Do margins of inline blocks and tables collapse? Check CSS\n *   spec. (They do currently; if not, ignoring them is simple.)\n *\n * - Lines which only contain a BREAK should be skipped for collapsing\n *   margins, or at least all three should collapse: the previous\n *   margin, the break, and the following margin. (Compare this with\n *   the CSS spec.)\n *\n * - Related to this: adding breaks should be revised.\n *   Textblock::addLinebreak and Textblock::addParbreak work quite\n *   differently, and Textblock::addParbreak seems much to complex for\n *   our needs, even when spaces of lines are kept.\n *\n *\n * <h3>Some Internals</h3>\n *\n * There are 4 lists, dw::Textblock::words, dw::Textblock::paragraphs,\n * dw::Textblock::lines, and dw::Textblock::anchors. The word list is\n * quite static; only new words may be added. A word is either text, a\n * widget, or a break.\n *\n * Lines refer to the word list (first and last). They are completely\n * redundant, i.e., they can be rebuilt from the words. Lines can be\n * rewrapped either completely or partially (see \"Incremental Resizing\"\n * below). For the latter purpose, several values are accumulated in the\n * lines. See dw::Textblock::Line for details.\n *\n * A recent change was the introduction of the paragraphs list, which\n * works quite similar, is also redundant, but is used to calculate\n * the extremes, not the size.\n *\n * Anchors associate the anchor name with the index of the next word at\n * the point of the anchor.\n *\n * <h3>Incremental Resizing</h3>\n *\n * dw::Textblock makes use of incremental resizing as described in \\ref\n * dw-widget-sizes. The parentRef is, for children of a dw::Textblock, simply\n * the number of the line. [<b>Update:</b> Incorrect; see \\ref dw-out-of-flow.]\n *\n * Generally, there are three cases which may change the size of the\n * widget:\n *\n * <ul>\n * <li> The line break size of the widget has changed, e.g., because the\n *      user has changed the size of the browser window. In this case,\n *      it is necessary to rewrap all the lines.\n *\n * <li> A child widget has changed its size. In this case, only a rewrap\n *      down from the line where this widget is located is necessary.\n *\n *      (This case is very important for tables. Tables are quite at the\n *      bottom, so that a partial rewrap is relevant. Otherwise, tables\n *      change their size quite often, so that this is necessary for a\n *      fast, non-blocking rendering)\n *\n * <li> A word (or widget, break etc.) is added to the text block. This\n *      makes it possible to reuse the old size by simply adjusting the\n *      current width and height, so no rewrapping is necessary.\n * </ul>\n *\n * The state of the size calculation is stored in wrapRef within\n * dw::Textblock, which has the value -1 if no rewrapping of lines\n * necessary, or otherwise the line from which a rewrap is necessary.\n *\n * <h3>Widgets Ouf Of Flow</h3>\n *\n * See\n *\n * - dw::oof::OOFAwareWidget (base class) and\n * - \\ref dw-out-of-flow.\n */\nclass Textblock: public RegardingBorder\n{\nprivate:\n   /**\n    * This class encapsulates the badness/penalty calculation, and so\n    * (i) makes changes (hopefully) simpler, and (ii) hides the\n    * integer arithmetic (floating point arithmetic avoided for\n    * performance reasons). Unfortunately, the value range of the\n    * badness is not well defined, so fiddling with the penalties is a\n    * bit difficult.\n    */\n\n   enum {\n      PENALTY_FORCE_BREAK = INT_MIN,\n      PENALTY_PROHIBIT_BREAK = INT_MAX\n   };\n\n   class BadnessAndPenalty\n   {\n   private:\n      enum { NOT_STRETCHABLE, QUITE_LOOSE, BADNESS_VALUE, TOO_TIGHT }\n         badnessState;\n      int ratio; // ratio is only defined when badness is defined\n      int badness, penalty[2];\n\n      // For debugging: define DEBUG for more informations in print().\n#ifdef DEBUG\n      int totalWidth, idealWidth, totalStretchability, totalShrinkability;\n#endif\n\n      // \"Infinity levels\" are used to represent very large numbers,\n      // including \"quasi-infinite\" numbers. A couple of infinity\n      // level and number can be mathematically represented as\n      //\n      //    number * N ^ (infinity level)\n      //\n      // where N is a number which is large enough. Practically,\n      // infinity levels are used to circumvent limited ranges for\n      // integer numbers.\n\n      // Here, all infinity levels have got special meanings.\n      enum {\n         INF_VALUE = 0,        /* simple values */\n         INF_LARGE,            /* large values, like QUITE_LOOSE */\n         INF_NOT_STRETCHABLE,  /* reserved for NOT_STRECTHABLE */\n         INF_TOO_TIGHT,        /* used for lines, which are too tight */\n         INF_PENALTIES,        /* used for penalties */\n         INF_MAX = INF_PENALTIES\n\n         // That INF_PENALTIES is the last value means that an\n         // infinite penalty (breaking is prohibited) makes a break\n         // not possible at all, so that pre-formatted text\n         // etc. works.\n      };\n\n      void setSinglePenalty (int index, int penalty);\n      int badnessValue (int infLevel);\n      int penaltyValue (int index, int infLevel);\n\n   public:\n      void calcBadness (int totalWidth, int idealWidth,\n                        int totalStretchability, int totalShrinkability);\n      inline void setPenalty (int penalty) { setPenalties (penalty, penalty); }\n      void setPenalties (int penalty1, int penalty2);\n\n      // Rather for debugging:\n      inline int getPenalty (int i) { return penalty[i]; }\n\n      bool lineLoose ();\n      bool lineTight ();\n      bool lineTooTight ();\n      bool lineMustBeBroken (int penaltyIndex);\n      bool lineCanBeBroken (int penaltyIndex);\n      int compareTo (int penaltyIndex, BadnessAndPenalty *other);\n\n      void intoStringBuffer(lout::misc::StringBuffer *sb);\n   };\n\n   enum { PENALTY_HYPHEN, PENALTY_EM_DASH_LEFT, PENALTY_EM_DASH_RIGHT,\n          PENALTY_NUM };\n   enum { NUM_DIV_CHARS = 6 };\n   enum { MAX_UNBROKEN_WORD_LENGTH = 16 };\n\n   typedef struct\n   {\n      const char *s;\n      bool charRemoved, canBeHyphenated, unbreakableForMinWidth;\n      int penaltyIndexLeft, penaltyIndexRight;\n   } DivChar;\n\n   static DivChar divChars[NUM_DIV_CHARS];\n\n   static const char *hyphenDrawChar;\n\nprotected:\n\n   /**\n    * \\brief Implementation used for words.\n    */\n   class WordImgRenderer:\n      public core::style::StyleImage::ExternalWidgetImgRenderer\n   {\n   protected:\n      Textblock *textblock;\n      int wordNo, xWordWidget, lineNo;\n      bool dataSet;\n\n   public:\n      WordImgRenderer (Textblock *textblock, int wordNo);\n      ~WordImgRenderer ();\n\n      void setData (int xWordWidget, int lineNo);\n\n      bool readyToDraw ();\n      void getBgArea (int *x, int *y, int *width, int *height);\n      void getRefArea (int *xRef, int *yRef, int *widthRef, int *heightRef);\n      core::style::Style *getStyle ();\n      void draw (int x, int y, int width, int height);\n   };\n\n   class SpaceImgRenderer: public WordImgRenderer\n   {\n   public:\n      inline SpaceImgRenderer (Textblock *textblock, int wordNo) :\n         WordImgRenderer (textblock, wordNo) { }\n\n      void getBgArea (int *x, int *y, int *width, int *height);\n      core::style::Style *getStyle ();\n   };\n\n   struct Paragraph\n   {\n      int firstWord;    /* first word's index in word vector */\n      int lastWord;     /* last word's index in word vector */\n\n      /*\n       * General remark: all values include the last hyphen width, but\n       * not the last space; these values are, however corrected, when\n       * another word is added.\n       *\n       * Also, as opposed to lines, paragraphs are created with the\n       * first, not the last word, so these values change when new\n       * words are added.\n       */\n\n      int parMin;       /* The sum of all word minima (plus spaces,\n                           hyphen width etc.) since the last possible\n                           break within this paragraph. */\n      int parMinIntrinsic;\n      int parAdjustmentWidth;\n      int parMax;       /* The sum of all word maxima in this\n                           paragraph (plus spaces, hyphen width\n                           etc.). */\n      int parMaxIntrinsic;\n\n      int maxParMin;    /* Maximum of all paragraph minima (value of\n                           \"parMin\"), including this paragraph. */\n      int maxParMinIntrinsic;\n      int maxParAdjustmentWidth;\n      int maxParMax;    /* Maximum of all paragraph maxima (value of\n                           \"parMax\"\"), including this paragraph. */\n      int maxParMaxIntrinsic;\n   };\n\n   struct Line\n   {\n      int firstWord;    /* first word's index in word vector */\n      int lastWord;     /* last word's index in word vector */\n\n\n      int top;                  /* \"top\" is always relative to the top of the\n                                   first line, i.e.  page->lines[0].top is\n                                   always 0. */\n      int marginAscent;         /* Maximum of all total ascents (including\n                                   margin: hence the name) of the words in this\n                                   line. */\n      int marginDescent;        /* Maximum of all total decents (including\n                                   margin: hence the name) of the words in this\n                                   line. */\n      int borderAscent;         /* Maximum of all ascents minus margin (but\n                                   including padding and border: hence the name)\n                                   of the words in this line. */\n      int borderDescent;        /* Maximum of all descents minus margin (but\n                                   including padding and border: hence the name)\n                                   of the words in this line. */\n      int contentAscent;        /* ??? (depricated?) */\n      int contentDescent;       /* ??? (depricated?) */\n      int breakSpace;           /* Space between this line and the next one. */\n      int textOffset;           /* Horizontal position of the first word of the\n                                   line, in widget coordinates. */\n\n      /**\n       * \\brief Returns the difference between two vertical lines\n       *    positions: height of this line plus space below this\n       *    line. The margin of the next line (marginAscent -\n       *    borderAscent) must be passed seperately.\n       */\n      inline int totalHeight (int marginNextLine)\n      { return borderAscent + borderDescent\n            // Collapsing of the margins of adjacent lines is done here:\n            + lout::misc::max (marginDescent - borderDescent, marginNextLine,\n                               breakSpace); }\n\n      /* Maximum of all line widths, including this line. Does not\n       * include the last space, but the last hyphen width. Please\n       * notice a change: until recently (before hyphenation and\n       * changed line breaking), the values were accumulated up to the\n       * last line, not this line.*/\n      int maxLineWidth;\n\n      /* The word index of the last OOF reference (most importantly:\n       * float) whic is positioned before this line, or -1, if there\n       * is no OOF reference positioned before.\n       *\n       * **Important:** These references may still be part of this or\n       * even a following line, when positioned before (this is the\n       * reason this attribute exists); see \\ref dw-out-of-flow. */\n      int lastOofRefPositionedBeforeThisLine;\n\n      int leftOffset, rightOffset;\n      enum { LEFT, RIGHT, CENTER } alignment;\n   };\n\n   struct Word\n   {\n      enum {\n         /** Can be hyphenated automatically. (Cleared after\n          * hyphenation.) */\n         CAN_BE_HYPHENATED         = 1 << 0,\n         /** Must be drawn with a hyphen, when at the end of the line. */\n         DIV_CHAR_AT_EOL           = 1 << 1,\n         /** Is or ends with a \"division character\", which is part of\n          * the word. */\n         PERM_DIV_CHAR             = 1 << 2,\n         /** This word must be drawn, together with the following\n          * word(s), by only one call of View::drawText(), to get\n          * kerning, ligatures etc. right. The last of the words drawn\n          * as one text does *not* have this flag set. */\n         DRAW_AS_ONE_TEXT          = 1 << 3,\n         /* When calculating the minimal width (as part of extremes),\n          * do not consider this word as breakable. This flag is\n          * ignored when the line is actually broken.  */\n         UNBREAKABLE_FOR_MIN_WIDTH = 1 << 4,\n         /* If a word represents a \"real\" text word, or (after\n          * hyphenation) the first part of a \"real\" text word, this\n          * flag is set. Plays a role for text transformation. */\n         WORD_START                = 1 << 5,\n         /* If a word represents a \"real\" text word, or (after\n          * hyphenation) the last part of a \"real\" text word, this\n          * flag is set. Analogue to WORD_START. */\n         WORD_END                  = 1 << 6,\n         /* This word is put at the top of the line, and at the\n          * left. This is necessary if the size of a child widget\n          * depends on the position, which, on the other hand, cannot\n          * be determined before the whole line is broken. */\n         TOPLEFT_OF_LINE           = 1 << 7\n      };\n\n      /* TODO: perhaps add a xLeft? */\n      core::Requisition size;\n      /* Space after the word, only if it's not a break: */\n      short origSpace; /* from font, set by addSpace */\n      short effSpace;  /* effective space, set by wordWrap,\n                        * used for drawing etc. */\n      short hyphenWidth; /* Additional width, when a word is part\n                          * (except the last part) of a hyphenationed\n                          * word. Has to be added to the width, when\n                          * this is the last word of the line, and\n                          * \"hyphenWidth > 0\" is also used to decide\n                          * whether to draw a hyphen. */\n      short flags;\n      core::Content content;\n\n      // accumulated values, relative to the beginning of the line\n      int totalWidth;          /* The sum of all word widths; plus all\n                                  spaces, excluding the one of this\n                                  word; plus the hyphen width of this\n                                  word (but of course, no hyphen\n                                  widths of previous words. In other\n                                  words: the value compared to the\n                                  ideal width of the line, if the line\n                                  would be broken after this word. */\n      int maxAscent, maxDescent;\n      int totalSpaceStretchability; // includes all *before* current word\n      int totalSpaceShrinkability;  // includes all *before* current word\n      BadnessAndPenalty badnessAndPenalty; /* when line is broken after this\n                                            * word */\n\n      core::style::Style *style;\n      core::style::Style *spaceStyle; /* initially the same as of the word,\n                                         later set by a_Dw_page_add_space */\n\n      // These two are used rarely, so there is perhaps a way to store\n      // them which is consuming less memory.\n      WordImgRenderer *wordImgRenderer;\n      SpaceImgRenderer *spaceImgRenderer;\n   };\n\n   struct Anchor\n   {\n      char *name;\n      int wordIndex;\n   };\n\n   class TextblockIterator: public OOFAwareWidgetIterator\n   {\n   protected:\n      int numContentsInFlow ();\n      void getContentInFlow (int index, core::Content *content);\n\n   public:\n      TextblockIterator (Textblock *textblock, core::Content::Type mask,\n                         bool atEnd);\n\n      static TextblockIterator *createWordIndexIterator\n        (Textblock *textblock, core::Content::Type mask, int wordIndex);\n\n      lout::object::Object *clone();\n\n      void highlight (int start, int end, core::HighlightLayer layer);\n      void unhighlight (int direction, core::HighlightLayer layer);\n      void getAllocation (int start, int end, core::Allocation *allocation);\n   };\n\n   friend class TextblockIterator;\n\n   /* These fields provide some ad-hoc-functionality, used by sub-classes. */\n   bool hasListitemValue; /* If true, the first word of the page is treated\n                          specially (search in source). */\n   int leftInnerPadding;  /* This is an additional padding on the left side\n                            (used by ListItem). */\n   int line1Offset;     /* This is an additional offset of the first line.\n                           May be negative (shift to left) or positive\n                           (shift to right). */\n   int line1OffsetEff; /* The \"effective\" value of line1_offset, may\n                          differ from line1_offset when\n                          ignoreLine1OffsetSometimes is set to true. */\n\n   /* The following is really hackish: It is used for DwTableCell (see\n    * comment in dw_table_cell.c), to avoid too wide table columns. If\n    * set to true, it has following effects:\n    *\n    *  (i) line1_offset is ignored in calculating the minimal width\n    *      (which is used by DwTable!), and\n    * (ii) line1_offset is ignored (line1_offset_eff is set to 0),\n    *      when line1_offset plus the width of the first word is\n    *      greater than the the line break witdh.\n    *\n    * \\todo Eliminate all these ad-hoc features by a new, simpler and\n    *       more elegant design. ;-)\n    */\n   bool ignoreLine1OffsetSometimes;\n\n   bool mustQueueResize;\n\n   /**\n    * The penalties for hyphens and other, multiplied by 100. So, 100\n    * means 1.0. INT_MAX and INT_MIN are also allowed. See\n    * dw::Textblock::BadnessAndPenalty::setPenalty for more\n    * details. Set from preferences.\n    */\n   static int penalties[PENALTY_NUM][2];\n\n   /**\n    * ...\n    */\n   static int stretchabilityFactor;\n\n   bool limitTextWidth; /* from preferences */\n   bool treatAsInline;\n   \n   int redrawY;\n   int lastWordDrawn;\n\n   core::SizeParams sizeRequestParams;\n   \n   /* Stores the value of getAvailWidth(). */\n   int lineBreakWidth;\n\n   int wrapRefLines, wrapRefParagraphs;  /* 0-based. Important: Both\n                                            are the line numbers, not\n                                            the value stored in\n                                            parentRef. */\n   int wrapRefLinesFCX, wrapRefLinesFCY;\n\n   // These four values are calculated by containingBlock->outOfFlowMgr\n   // (when defined; otherwise, they are  false, or 0, respectively), for\n   // the newly constructed line, only when needed: when a new line is\n   // added, or if something in the line currently constucted has\n   // changed, e. g. a float has been added.\n\n   bool newLineHasFloatLeft, newLineHasFloatRight;\n   int newLineLeftBorder, newLineRightBorder; /* As returned by\n                                                 outOfFlowMgr->get...Border,\n                                                 or 0, if outOfFlowMgr\n                                                 is NULL */\n   int newLineLeftFloatHeight, newLineRightFloatHeight;\n\n   // Ascent and descent of the newly constructed line, i. e. maximum\n   // of all words ascent/descent since the end of the last line. Not\n   // neccessary the ascent and descent of the newly added line, since\n   // not all words are added to it.\n   int newLineAscent, newLineDescent;\n\n   lout::misc::SimpleVector <Line> *lines;\n   lout::misc::SimpleVector <Paragraph> *paragraphs;\n   int nonTemporaryLines;\n   lout::misc::NotSoSimpleVector <Word> *words;\n   lout::misc::SimpleVector <Anchor> *anchors;\n\n   struct { int index, nChar; }\n      hlStart[core::HIGHLIGHT_NUM_LAYERS], hlEnd[core::HIGHLIGHT_NUM_LAYERS];\n\n   int hoverLink;  /* The link under the mouse pointer */\n\n   int numSizeReferences;\n   Widget *sizeReferences[NUM_OOFM];\n   \n   void queueDrawRange (int index1, int index2);\n   int calcVerticalBorder (int widgetPadding, int widgetBorder,\n                           int widgetMargin, int lineBorderTotal,\n                           int lineMarginTotal);\n   void getWordExtremes (Word *word, core::Extremes *extremes);\n   void justifyLine (Line *line, int diff);\n   Line *addLine (int firstWord, int lastWord, int newLastOofPos,\n                  bool temporary, int minHeight);\n   void rewrap ();\n   void fillParagraphs ();\n   void initNewLine ();\n   void calcBorders (int lastOofRef, int height);\n   void showMissingLines ();\n   void removeTemporaryLines ();\n\n   void decorateText (core::View *view, core::style::Style *style,\n                      core::style::Color::Shading shading,\n                      int x, int yBase, int width);\n   void drawText (core::View *view, core::style::Style *style,\n                  core::style::Color::Shading shading, int x, int y,\n                  const char *text, int start, int len, bool isStart,\n                  bool isEnd);\n   void drawWord (Line *line, int wordIndex1, int wordIndex2, core::View *view,\n                  core::Rectangle *area, int xWidget, int yWidgetBase);\n   void drawWord0 (int wordIndex1, int wordIndex2,\n                   const char *text, int totalWidth, bool drawHyphen,\n                   core::style::Style *style, core::View *view,\n                   core::Rectangle *area, int xWidget, int yWidgetBase);\n   void drawSpace (int wordIndex, core::View *view, core::Rectangle *area,\n                   int xWidget, int yWidgetBase);\n   void drawLine (Line *line, core::View *view, core::Rectangle *area,\n                  core::DrawingContext *context);\n\n   int findLineIndex (int y);\n   int findLineIndexWhenNotAllocated (int y);\n   int findLineIndexWhenAllocated (int y);\n   int findLineIndex (int y, int ascent);\n   int findLineOfWord (int wordIndex);\n   int findParagraphOfWord (int wordIndex);\n   Word *findWord (int x, int y, bool *inSpace);\n\n   Word *addWord (int width, int ascent, int descent, short flags,\n                  core::style::Style *style);\n   void breakAdded ();\n   void initWord (int wordNo);\n   void cleanupWord (int wordNo);\n   void removeWordImgRenderer (int wordNo);\n   void setWordImgRenderer (int wordNo);\n   void removeSpaceImgRenderer (int wordNo);\n   void setSpaceImgRenderer (int wordNo);\n   void fillWord (int wordNo, int width, int ascent, int descent,\n                  short flags, core::style::Style *style);\n   void fillSpace (int wordNo, core::style::Style *style);\n   void setBreakOption (Word *word, core::style::Style *style,\n                        int breakPenalty1, int breakPenalty2, bool forceBreak);\n   bool isBreakAllowedInWord (Word *word)\n   { return isBreakAllowed (word->style); }\n   bool isBreakAllowed (core::style::Style *style);\n   int textWidth (const char *text, int start, int len,\n                  core::style::Style *style, bool isStart, bool isEnd);\n   void calcTextSize (const char *text, size_t len, core::style::Style *style,\n                      core::Requisition *size, bool isStart, bool isEnd);\n   bool calcSizeOfWidgetInFlow (int wordIndex, Widget *widget,\n                                core::Requisition *size);\n   bool findSizeRequestReference (Widget *reference, int *xRef = NULL,\n                                  int *yRef = NULL);\n   bool findSizeRequestReference (int oofmIndex, int *xRef = NULL,\n                                  int *yRef = NULL)\n   { return findSizeRequestReference (oofContainer[oofmIndex], xRef, yRef); }\n\n   /**\n    * Of nested text blocks, only the most inner one must regard the\n    * borders of floats.\n    */\n   inline bool mustBorderBeRegarded (Line *line)\n   {\n      return getWidgetRegardingBorderForLine (line) == NULL;\n   }\n\n   inline bool mustBorderBeRegarded (int lineNo)\n   {\n      return getWidgetRegardingBorderForLine (lineNo) == NULL;\n   }\n\n   // The following methods return the y offset of a line,\n   // - given as pointer or by index;\n   // - either within the canvas, or within this widget;\n   // - with allocation passed explicitely, or using the widget allocation\n   //   (important: this is set *after* sizeRequestImpl is returning.\n\n   inline int lineYOffsetWidget (Line *line, core::Allocation *allocation)\n   {\n      return line->top + (allocation->ascent - lines->getRef(0)->borderAscent);\n   }\n\n   inline int lineYOffsetWidget (Line *line)\n   {\n      return lineYOffsetWidget (line, &allocation);\n   }\n\n   inline int lineYOffsetCanvas (Line *line, core::Allocation *allocation)\n   {\n      return allocation->y + lineYOffsetWidget (line, allocation);\n   }\n\n   inline int lineYOffsetCanvas (Line *line)\n   {\n      return lineYOffsetCanvas (line, &allocation);\n   }\n\n   inline int lineYOffsetWidget (int lineIndex)\n   {\n      return lineYOffsetWidget (lines->getRef (lineIndex));\n   }\n\n   inline int lineYOffsetWidget (int lineIndex, core::Allocation *allocation)\n   {\n      return lineYOffsetWidget (lines->getRef (lineIndex), allocation);\n   }\n\n   inline int lineYOffsetCanvas (int lineIndex)\n   {\n      return lineYOffsetCanvas (lines->getRef (lineIndex));\n   }\n\n   inline int calcPenaltyIndexForNewLine ()\n   {\n      if (lines->size() == 0)\n         return 0;\n      else {\n         Line *line = lines->getLastRef();\n         if (line->firstWord <= line->lastWord)\n            return\n               (words->getRef(line->lastWord)->flags &\n                (Word::DIV_CHAR_AT_EOL | Word::PERM_DIV_CHAR)) ? 1 : 0;\n         else\n            // empty line\n            return 0;\n      }\n   }\n\n   RegardingBorder *getWidgetRegardingBorderForLine (Line *line);\n   RegardingBorder *getWidgetRegardingBorderForLine (int lineNo);\n   RegardingBorder *getWidgetRegardingBorderForLine (int firstWord,\n                                                     int lastWord);\n   int yOffsetOfLineToBeCreated (int *lastMargin = NULL);\n   int yOffsetOfLineCreated (Line *line);\n\n   bool sendSelectionEvent (core::SelectionState::EventType eventType,\n                            core::MousePositionEvent *event);\n\n   void processWord (int wordIndex);\n   \n   virtual int wordWrap (int wordIndex, bool wrapAll);\n\n   int wrapWordInFlow (int wordIndex, bool wrapAll);\n   int wrapWordOofRef (int wordIndex, bool wrapAll);\n   void balanceBreakPosAndHeight (int wordIndex, int firstIndex,\n                                  int *searchUntil, bool tempNewLine,\n                                  int penaltyIndex, bool borderIsCalculated,\n                                  bool *thereWillBeMoreSpace, bool wrapAll,\n                                  int *diffWords, int *wordIndexEnd,\n                                  int *lastFloatPos, bool regardBorder,\n                                  int *height, int *breakPos);\n   int searchBreakPos (int wordIndex, int firstIndex, int *searchUntil,\n                       bool tempNewLine, int penaltyIndex,\n                       bool thereWillBeMoreSpace, bool wrapAll,\n                       int *diffWords, int *wordIndexEnd,\n                       int *addIndex1 = NULL);\n   int searchMinBap (int firstWord, int lastWordm, int penaltyIndex,\n                     bool thereWillBeMoreSpace, bool correctAtEnd);\n   int considerHyphenation (int firstIndex, int breakPos);\n   bool isHyphenationCandidate (Word *word);\n   int calcLinePartHeight (int firstWord, int lastWord);\n\n   void handleWordExtremes (int wordIndex);\n   void correctLastWordExtremes ();\n\n   static int getSpaceShrinkability(struct Word *word);\n   static int getSpaceStretchability(struct Word *word);\n   int getLineShrinkability(int lastWordIndex);\n   int getLineStretchability(int lastWordIndex);\n   int hyphenateWord (int wordIndex, int *addIndex1 = NULL);\n   void moveWordIndices (int wordIndex, int num, int *addIndex1 = NULL);\n   void accumulateWordForLine (int lineIndex, int wordIndex);\n   void accumulateWordData (int wordIndex);\n   int calcLineBreakWidth (int lineIndex);\n   void initLine1Offset (int wordIndex);\n   void alignLine (int lineIndex);\n   void calcTextOffset (int lineIndex, int totalWidth);\n\n   void drawLevel (core::View *view, core::Rectangle *area, int level,\n                   core::DrawingContext *context);\n\n   Widget *getWidgetAtPointLevel (int x, int y, int level,\n                                  core::GettingWidgetAtPointContext *context);\n\n   void sizeRequestImpl (core::Requisition *requisition, int numPos,\n                         Widget **references, int *x, int *y);\n   int numSizeRequestReferences ();\n   Widget *sizeRequestReference (int index);\n\n   void getExtremesSimpl (core::Extremes *extremes);\n\n   int numGetExtremesReferences ();\n   Widget *getExtremesReference (int index);\n\n   void notifySetAsTopLevel ();\n   void notifySetParent ();\n\n   void sizeAllocateImpl (core::Allocation *allocation);\n\n   void calcExtraSpaceImpl (int numPos, Widget **references, int *x, int *y);\n\n   int getAvailWidthOfChild (core::Widget *child, bool forceValue);\n   int getAvailHeightOfChild (core::Widget *child, bool forceValue);\n\n   void containerSizeChangedForChildren ();\n   bool affectsSizeChangeContainerChild (Widget *child);\n   bool usesAvailWidth ();\n   void resizeDrawImpl ();\n\n   void markSizeChange (int ref);\n   void markExtremesChange (int ref);\n\n   bool isBlockLevel ();\n\n   bool buttonPressImpl (core::EventButton *event);\n   bool buttonReleaseImpl (core::EventButton *event);\n   bool motionNotifyImpl (core::EventMotion *event);\n   void enterNotifyImpl (core::EventCrossing *event);\n   void leaveNotifyImpl (core::EventCrossing *event);\n\n   void removeChild (Widget *child);\n\n   void addText0 (const char *text, size_t len, short flags,\n                  core::style::Style *style, core::Requisition *size);\n   void calcTextSizes (const char *text, size_t textLen,\n                       core::style::Style *style,\n                       int numBreaks, int *breakPos,\n                       core::Requisition *wordSize);\n\n   int getGeneratorRest (int oofmIndex);\n\npublic:\n   static int CLASS_ID;\n\n   static void setPenaltyHyphen (int penaltyHyphen);\n   static void setPenaltyHyphen2 (int penaltyHyphen2);\n   static void setPenaltyEmDashLeft (int penaltyLeftEmDash);\n   static void setPenaltyEmDashRight (int penaltyRightEmDash);\n   static void setPenaltyEmDashRight2 (int penaltyRightEmDash2);\n   static void setStretchabilityFactor (int stretchabilityFactor);\n\n   static inline bool mustAddBreaks (core::style::Style *style)\n   { return !testStyleOutOfFlow (style) ||\n         testStyleRelativelyPositioned (style); }\n\n   Textblock (bool limitTextWidth, bool treatAsInline = false);\n   ~Textblock ();\n\n   core::Iterator *iterator (core::Content::Type mask, bool atEnd);\n\n   void flush ();\n\n   void addText (const char *text, size_t len, core::style::Style *style);\n   inline void addText (const char *text, core::style::Style *style)\n   { addText (text, strlen(text), style); }\n   void addWidget (core::Widget *widget, core::style::Style *style);\n   bool addAnchor (const char *name, core::style::Style *style);\n   void addSpace (core::style::Style *style);\n   void addBreakOption (core::style::Style *style, bool forceBreak);\n   void addParbreak (int space, core::style::Style *style);\n   void addLinebreak (core::style::Style *style);\n\n   void handOverBreak (core::style::Style *style);\n   void changeLinkColor (int link, int newColor);\n   void changeWordStyle (int from, int to, core::style::Style *style,\n                         bool includeFirstSpace, bool includeLastSpace);\n  \n   void updateReference (int ref);\n   void widgetRefSizeChanged (int externalIndex);\n   void oofSizeChanged (bool extremesChanged);\n   int getGeneratorX (int oofmIndex);\n   int getGeneratorY (int oofmIndex);\n   int getGeneratorWidth ();\n   int getMaxGeneratorWidth ();\n   bool usesMaxGeneratorWidth ();\n   bool isPossibleOOFContainer (int oofmIndex);\n   bool isPossibleOOFContainerParent (int oofmIndex);\n};\n\n#define DBG_SET_WORD_PENALTY(n, i, is) \\\n   D_STMT_START { \\\n      if (words->getRef(n)->badnessAndPenalty.getPenalty (i) == INT_MIN) \\\n         DBG_OBJ_ARRATTRSET_SYM (\"words\", n, \"penalty.\" is, \"-inf\"); \\\n      else if (words->getRef(n)->badnessAndPenalty.getPenalty (i) == INT_MAX) \\\n         DBG_OBJ_ARRATTRSET_SYM (\"words\", n, \"penalty.\" is, \"inf\"); \\\n      else \\\n         DBG_OBJ_ARRATTRSET_NUM (\"words\", n, \"penalty.\" is, \\\n                                 words->getRef(n)->badnessAndPenalty \\\n                                 .getPenalty (i)); \\\n   } D_STMT_END\n\n#ifdef DBG_RTFL\n#define DBG_OBJ_ARRATTRSET_WREF(var, ind, attr, wref) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d.s:p (p, d)\", this, var, ind, attr, wref, \\\n                   wref->widget, wref->parentRef)\n#else\n#define DBG_OBJ_ARRATTRSET_WREF(var, ind, attr, wref) STMT_NOP\n#endif\n   \n#define DBG_SET_WORD(n) \\\n   D_STMT_START { \\\n      switch (words->getRef(n)->content.type) { \\\n      case ::dw::core::Content::TEXT: \\\n         DBG_OBJ_ARRATTRSET_SYM (\"words\", n, \"type\", \"TEXT\"); \\\n         DBG_OBJ_ARRATTRSET_STR (\"words\", n, \\\n                                 \"text/widget/widgetReference/breakSpace\", \\\n                                 words->getRef(n)->content.text); \\\n         break; \\\n      case ::dw::core::Content::WIDGET_IN_FLOW: \\\n         DBG_OBJ_ARRATTRSET_SYM (\"words\", n, \"type\", \"WIDGET_IN_FLOW\"); \\\n         DBG_OBJ_ARRATTRSET_PTR (\"words\", n, \\\n                                 \"text/widget/widgetReference/breakSpace\", \\\n                                 words->getRef(n)->content.widget); \\\n         break; \\\n      case ::dw::core::Content::WIDGET_OOF_REF: \\\n         DBG_OBJ_ARRATTRSET_SYM (\"words\", n, \"type\", \"WIDGET_OOF_REF\"); \\\n         DBG_OBJ_ARRATTRSET_WREF (\"words\", n, \\\n                                  \"text/widget/widgetReference/breakSpace\", \\\n                                  words->getRef(n)->content.widgetReference); \\\n         break; \\\n      case ::dw::core::Content::BREAK: \\\n         DBG_OBJ_ARRATTRSET_SYM (\"words\", n, \"type\", \"BREAK\"); \\\n         DBG_OBJ_ARRATTRSET_NUM (\"words\", n,  \\\n                                 \"text/widget/widgetReference/breakSpace\", \\\n                                 words->getRef(n)->content.breakSpace); \\\n         break; \\\n      default: \\\n         DBG_OBJ_ARRATTRSET_SYM (\"words\", n, \"type\", \"???\"); \\\n         DBG_OBJ_ARRATTRSET_SYM (\"words\", n, \\\n                                 \"text/widget/widgetReference/breakSpace\", \\\n                                 \"???\"); \\\n      } \\\n      DBG_SET_WORD_PENALTY (n, 0, \"0\"); \\\n      DBG_SET_WORD_PENALTY (n, 1, \"1\"); \\\n   } D_STMT_END\n\n#define DBG_SET_WORD_SIZE(n) \\\n   D_STMT_START { \\\n      DBG_OBJ_ARRATTRSET_NUM (\"words\", n, \"size.width\", \\\n                              words->getRef(n)->size.width); \\\n      DBG_OBJ_ARRATTRSET_NUM (\"words\", n, \"size.ascent\", \\\n                              words->getRef(n)->size.ascent); \\\n      DBG_OBJ_ARRATTRSET_NUM (\"words\", n, \"size.descent\", \\\n                              words->getRef(n)->size.descent); \\\n   } D_STMT_END\n\n#define DBG_MSG_WORD(aspect, prio, prefix, n, suffix) \\\n   D_STMT_START { \\\n      if ((n) < 0 || (n) >= words->size ()) \\\n         DBG_OBJ_MSG (aspect, prio, prefix \"undefined (wrong index)\" suffix); \\\n      else { \\\n         switch (words->getRef(n)->content.type) { \\\n         case ::dw::core::Content::TEXT: \\\n            DBG_OBJ_MSGF (aspect, prio, prefix \"TEXT / \\\"%s\\\"\" suffix, \\\n                          words->getRef(n)->content.text); \\\n            break; \\\n         case ::dw::core::Content::WIDGET_IN_FLOW: \\\n            DBG_OBJ_MSGF (aspect, prio, prefix \"WIDGET_IN_FLOW / %p\" suffix, \\\n                          words->getRef(n)->content.widget); \\\n            break; \\\n         case ::dw::core::Content::WIDGET_OOF_REF: \\\n            DBG_OBJ_MSGF (aspect, prio, \\\n                          prefix \"WIDGET_OOF_REF / %p (%p, %d)\" suffix,\\\n                          words->getRef(n)->content.widgetReference,    \\\n                          words->getRef(n)->content.widgetReference->widget, \\\n                          words->getRef(n)->content.widgetReference \\\n                          ->parentRef); \\\n            break; \\\n         case ::dw::core::Content::BREAK: \\\n            DBG_OBJ_MSGF (aspect, prio, prefix \"BREAK / %d\" suffix, \\\n                          words->getRef(n)->content.breakSpace); \\\n            break; \\\n         default: \\\n            DBG_OBJ_MSG (aspect, prio, prefix \"??? / ???\" suffix); \\\n         } \\\n      } \\\n   } D_STMT_END\n\n} // namespace dw\n\n#endif // __DW_TEXTBLOCK_HH__\n"
  },
  {
    "path": "dw/textblock_iterator.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007, 2012-2013 Sebastian Geerken <sgeerken@dillo.org>\n *\n * (Parts of this file were originally part of textblock.cc.)\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"textblock.hh\"\n#include \"../lout/msg.h\"\n#include \"../lout/misc.hh\"\n\n#include <stdio.h>\n#include <math.h>\n\nusing namespace lout;\n\nnamespace dw {\n\nTextblock::TextblockIterator::TextblockIterator (Textblock *textblock,\n                                                 core::Content::Type mask,\n                                                 bool atEnd):\n   OOFAwareWidgetIterator (textblock, mask, atEnd, textblock->words->size ())\n{\n}\n\nTextblock::TextblockIterator\n   *Textblock::TextblockIterator::createWordIndexIterator (Textblock *textblock,\n                                                           core::Content::Type\n                                                           mask,\n                                                           int wordIndex)\n{\n   TextblockIterator *tbIt = new TextblockIterator (textblock, mask, false);\n   tbIt->setValues (0, wordIndex);\n   return tbIt;\n}\n\nobject::Object *Textblock::TextblockIterator::clone()\n{\n   TextblockIterator *tbIt =\n      new TextblockIterator ((Textblock*)getWidget(), getMask(), false);\n   cloneValues (tbIt);\n   return tbIt;\n}\n\nvoid Textblock::TextblockIterator::highlight (int start, int end,\n                                              core::HighlightLayer layer)\n{\n   DBG_OBJ_ENTER_O (\"iterator\", 0, getWidget (), \"TextblockIterator::highlight\",\n                    \"..., %d, %d, %d\", start, end, layer);\n\n   DBG_IF_RTFL {\n      misc::StringBuffer sb;\n      intoStringBuffer (&sb);\n      DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget (), \"iterator: %s\",\n                      sb.getChars ());\n   }\n\n   if (inFlow ()) {\n      DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget (), \"in-flow index: %d\",\n                      getInFlowIndex ());\n\n      Textblock *textblock = (Textblock*)getWidget();\n      int index = getInFlowIndex (), index1 = index, index2 = index;\n\n      int oldStartIndex = textblock->hlStart[layer].index;\n      int oldStartChar = textblock->hlStart[layer].nChar;\n      int oldEndIndex = textblock->hlEnd[layer].index;\n      int oldEndChar = textblock->hlEnd[layer].nChar;\n\n      if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index) {\n         /* nothing is highlighted */\n         textblock->hlStart[layer].index = index;\n         textblock->hlEnd[layer].index = index;\n      }\n\n      if (textblock->hlStart[layer].index >= index) {\n         index2 = textblock->hlStart[layer].index;\n         textblock->hlStart[layer].index = index;\n         textblock->hlStart[layer].nChar = start;\n      }\n\n      if (textblock->hlEnd[layer].index <= index) {\n         index2 = textblock->hlEnd[layer].index;\n         textblock->hlEnd[layer].index = index;\n         textblock->hlEnd[layer].nChar = end;\n      }\n\n      DBG_OBJ_ARRATTRSET_NUM_O (textblock, \"hlStart\", layer, \"index\",\n                                textblock->hlStart[layer].index);\n      DBG_OBJ_ARRATTRSET_NUM_O (textblock, \"hlStart\", layer, \"nChar\",\n                                textblock->hlStart[layer].nChar);\n      DBG_OBJ_ARRATTRSET_NUM_O (textblock, \"hlEnd\", layer, \"index\",\n                                textblock->hlEnd[layer].index);\n      DBG_OBJ_ARRATTRSET_NUM_O (textblock, \"hlEnd\", layer, \"nChar\",\n                                textblock->hlEnd[layer].nChar);\n\n      if (oldStartIndex != textblock->hlStart[layer].index ||\n          oldStartChar != textblock->hlStart[layer].nChar ||\n          oldEndIndex != textblock->hlEnd[layer].index ||\n          oldEndChar != textblock->hlEnd[layer].nChar)\n         textblock->queueDrawRange (index1, index2);\n   } else\n      highlightOOF (start, end, layer);\n\n      DBG_OBJ_LEAVE_O (getWidget ());\n}\n\nvoid Textblock::TextblockIterator::unhighlight (int direction,\n                                                core::HighlightLayer layer)\n{\n   DBG_OBJ_ENTER_O (\"iterator\", 0, getWidget (),\n                    \"TextblockIterator/unhighlight\", \"..., %d, %d\",\n                    direction, layer);\n\n   DBG_IF_RTFL {\n      misc::StringBuffer sb;\n      intoStringBuffer (&sb);\n      DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget (), \"iterator: %s\",\n                      sb.getChars ());\n   }\n\n   if (inFlow ()) {\n      DBG_OBJ_MSGF_O (\"iterator\", 1, getWidget (), \"in-flow index: %d\",\n                      getInFlowIndex ());\n\n      Textblock *textblock = (Textblock*)getWidget();\n      int index = getInFlowIndex (), index1 = index, index2 = index;\n\n      if (textblock->hlStart[layer].index > textblock->hlEnd[layer].index)\n         return;\n\n      int oldStartIndex = textblock->hlStart[layer].index;\n      int oldStartChar = textblock->hlStart[layer].nChar;\n      int oldEndIndex = textblock->hlEnd[layer].index;\n      int oldEndChar = textblock->hlEnd[layer].nChar;\n\n      if (direction == 0) {\n         index1 = textblock->hlStart[layer].index;\n         index2 = textblock->hlEnd[layer].index;\n         textblock->hlStart[layer].index = 1;\n         textblock->hlEnd[layer].index = 0;\n      } else if (direction > 0 && textblock->hlStart[layer].index <= index) {\n         index1 = textblock->hlStart[layer].index;\n         textblock->hlStart[layer].index = index + 1;\n         textblock->hlStart[layer].nChar = 0;\n      } else if (direction < 0 && textblock->hlEnd[layer].index >= index) {\n         index1 = textblock->hlEnd[layer].index;\n         textblock->hlEnd[layer].index = index - 1;\n         textblock->hlEnd[layer].nChar = INT_MAX;\n      }\n\n      DBG_OBJ_ARRATTRSET_NUM_O (textblock, \"hlStart\", layer, \"index\",\n                                textblock->hlStart[layer].index);\n      DBG_OBJ_ARRATTRSET_NUM_O (textblock, \"hlStart\", layer, \"nChar\",\n                                textblock->hlStart[layer].nChar);\n      DBG_OBJ_ARRATTRSET_NUM_O (textblock, \"hlEnd\", layer, \"index\",\n                                textblock->hlEnd[layer].index);\n      DBG_OBJ_ARRATTRSET_NUM_O (textblock, \"hlEnd\", layer, \"nChar\",\n                                textblock->hlEnd[layer].nChar);\n\n      if (oldStartIndex != textblock->hlStart[layer].index ||\n          oldStartChar != textblock->hlStart[layer].nChar ||\n          oldEndIndex != textblock->hlEnd[layer].index ||\n          oldEndChar != textblock->hlEnd[layer].nChar)\n         textblock->queueDrawRange (index1, index2);\n   } else\n      unhighlightOOF (direction, layer);\n\n   DBG_OBJ_LEAVE_O (getWidget ());\n}\n\nvoid Textblock::TextblockIterator::getAllocation (int start, int end,\n                                                  core::Allocation *allocation)\n{\n   if (inFlow ()) {\n      Textblock *textblock = (Textblock*)getWidget();\n\n      int index = getInFlowIndex ();\n      Word *word = textblock->words->getRef (index);\n      int firstWordOfLine, textOffset, lineYOffsetCanvas, lineBorderAscent;\n\n      int lineIndex = textblock->findLineOfWord (index);\n\n      // It may be that the line does not exist yet.\n      if (lineIndex != -1) {\n         // Line exists: simple.\n         Line *line = textblock->lines->getRef (lineIndex);\n         firstWordOfLine = line->firstWord;\n         textOffset = line->textOffset;\n         lineYOffsetCanvas = textblock->lineYOffsetCanvas (line);\n         lineBorderAscent = line->borderAscent;\n      } else {        \n         // Line does not exist. Calculate the values in a similar way as in\n         // Textblock::addLine().\n         Line *prevLine = textblock->lines->size () > 0 ?\n            textblock->lines->getLastRef () : NULL;\n         firstWordOfLine = prevLine ? prevLine->lastWord + 1 : 0;\n\n         // The variable textOffset, defined below, is what Line::leftOffset\n         // will be for the next line; Line::textOffset itself cannot be\n         // calculated before the line is complete.\n         bool regardBorder =\n             textblock->mustBorderBeRegarded (textblock->lines->size ());\n         textOffset =\n            misc::max (regardBorder ?  textblock->newLineLeftBorder : 0,\n                       textblock->boxOffsetX () +  textblock->leftInnerPadding\n                       + (textblock->lines->size () == 0 ?\n                             textblock->line1OffsetEff : 0));\n\n         lineYOffsetCanvas = textblock->yOffsetOfLineToBeCreated ();\n\n         lineBorderAscent = 0;\n         for (int i = firstWordOfLine; i < textblock->words->size (); i++) {\n            Word *w = textblock->words->getRef (i);\n            int borderAscent =\n               w->content.type == core::Content::WIDGET_IN_FLOW ?\n               w->size.ascent - w->content.widget->getStyle()->margin.top :\n               w->size.ascent;\n            lineBorderAscent = misc::max (lineBorderAscent, borderAscent);\n         }\n      }\n\n      allocation->x = textblock->allocation.x + textOffset;\n      for (int i = firstWordOfLine; i < index; i++) {\n         Word *w = textblock->words->getRef(i);\n         allocation->x += w->size.width + w->effSpace;\n      }\n      if (start > 0 && word->content.type == core::Content::TEXT) {\n         allocation->x += textblock->textWidth (word->content.text, 0, start,\n                                                word->style,\n                                                word->flags & Word::WORD_START,\n                                                (word->flags & Word::WORD_END)\n                                                && word->content.text[start]\n                                                == 0);\n      }\n      allocation->y = lineYOffsetCanvas + lineBorderAscent - word->size.ascent;\n\n      allocation->width = word->size.width;\n      if (word->content.type == core::Content::TEXT) {\n         int wordEnd = strlen(word->content.text);\n\n         if (start > 0 || end < wordEnd) {\n            end = misc::min(end, wordEnd); /* end could be INT_MAX */\n            allocation->width =\n               textblock->textWidth (word->content.text, start, end - start,\n                                     word->style,\n                                     (word->flags & Word::WORD_START)\n                                     && start == 0,\n                                     (word->flags & Word::WORD_END)\n                                     && word->content.text[end] == 0);\n         }\n      }\n      allocation->ascent = word->size.ascent;\n      allocation->descent = word->size.descent;\n   } else\n      getAllocationOOF (start, end, allocation);\n}\n\nint Textblock::TextblockIterator::numContentsInFlow ()\n{\n   return ((Textblock*)getWidget())->words->size ();\n}\n\nvoid Textblock::TextblockIterator::getContentInFlow (int index,\n                                                     core::Content *content)\n{\n   *content = ((Textblock*)getWidget())->words->getRef(index)->content;\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/textblock_linebreaking.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007, 2012-2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * (Parts of this file were originally part of textblock.cc.)\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"textblock.hh\"\n#include \"hyphenator.hh\"\n#include \"../lout/msg.h\"\n#include \"../lout/debug.hh\"\n#include \"../lout/misc.hh\"\n\n#include <stdio.h>\n#include <math.h>\n\nusing namespace lout;\nusing namespace lout::misc;\n\nnamespace dw {\n\nint Textblock::BadnessAndPenalty::badnessValue (int infLevel)\n{\n   switch (badnessState) {\n   case NOT_STRETCHABLE:\n      return infLevel == INF_NOT_STRETCHABLE ? 1 : 0;\n\n   case QUITE_LOOSE:\n      return infLevel == INF_LARGE ? 1 : 0;\n\n   case BADNESS_VALUE:\n      return infLevel == INF_VALUE ? badness : 0;\n\n   case TOO_TIGHT:\n      return infLevel == INF_TOO_TIGHT ? 1 : 0;\n   }\n\n   // compiler happiness\n   assertNotReached ();\n   return 0;\n}\n\nint Textblock::BadnessAndPenalty::penaltyValue (int index, int infLevel)\n{\n   if (penalty[index] == INT_MIN)\n      return infLevel == INF_PENALTIES ? -1 : 0;\n   else if (penalty[index] == INT_MAX)\n      return infLevel == INF_PENALTIES ? 1 : 0;\n   else\n      return  infLevel == INF_VALUE ? penalty[index] : 0;\n}\n\nvoid Textblock::BadnessAndPenalty::calcBadness (int totalWidth, int idealWidth,\n                                                int totalStretchability,\n                                                int totalShrinkability)\n{\n#ifdef DEBUG\n   this->totalWidth = totalWidth;\n   this->idealWidth = idealWidth;\n   this->totalStretchability = totalStretchability;\n   this->totalShrinkability = totalShrinkability;\n#endif\n\n   ratio = 0;\n\n   if (totalWidth == idealWidth) {\n      badnessState = BADNESS_VALUE;\n      badness = 0;\n   } else if (totalWidth < idealWidth) {\n      if (totalStretchability == 0)\n         badnessState = NOT_STRETCHABLE;\n      else {\n         ratio = 100 * (idealWidth - totalWidth) / totalStretchability;\n         if (ratio > 1024)\n            badnessState = QUITE_LOOSE;\n         else {\n            badnessState = BADNESS_VALUE;\n            badness = ratio * ratio * ratio;\n         }\n      }\n   } else { // if (totalWidth > idealWidth)\n      if (totalShrinkability == 0)\n         badnessState = TOO_TIGHT;\n      else {\n         // ratio is negative here\n         ratio = 100 * (idealWidth - totalWidth) / totalShrinkability;\n         if (ratio <= - 100)\n            badnessState = TOO_TIGHT;\n         else {\n            badnessState = BADNESS_VALUE;\n            badness = - ratio * ratio * ratio;\n         }\n      }\n   }\n}\n\n/**\n * Sets the penalty, multiplied by 100. Multiplication is necessary\n * to deal with fractional numbers, without having to use floating\n * point numbers. So, to set a penalty to 0.5, pass 50.\n *\n * INT_MAX and INT_MIN (representing inf and -inf, respectively) are\n * also allowed.\n *\n * The definition of penalties depends on the definition of badness,\n * which adheres to the description in \\ref dw-line-breaking, section\n * \"Criteria for Line-Breaking\". The exact calculation may vary, but\n * this definition of should be rather stable: (i)&nbsp;A perfectly\n * fitting line has a badness of 0. (ii)&nbsp;A line, where all spaces\n * are extended by exactly the stretchability, as well as a line, where\n * all spaces are reduced by the shrinkability, have a badness of 1.\n *\n * (TODO plural: penalties, not penalty. Correct above comment)\n */\nvoid Textblock::BadnessAndPenalty::setPenalties (int penalty1, int penalty2)\n{\n   // TODO Check here some cases, e.g. both or no penalty INT_MIN.\n   setSinglePenalty(0, penalty1);\n   setSinglePenalty(1, penalty2);\n}\n\nvoid Textblock::BadnessAndPenalty::setSinglePenalty (int index, int penalty)\n{\n   if (penalty == INT_MAX || penalty == INT_MIN)\n      this->penalty[index] = penalty;\n   else\n      // This factor consists of: (i) 100^3, since in calcBadness(), the\n      // ratio is multiplied with 100 (again, to use integer numbers for\n      // fractional numbers), and the badness (which has to be compared\n      // to the penalty!) is the third power or it; (ii) the denominator\n      // 100, of course, since 100 times the penalty is passed to this\n      // method.\n      this->penalty[index] = penalty * (100 * 100 * 100 / 100);\n}\n\nbool Textblock::BadnessAndPenalty::lineLoose ()\n{\n   return\n      badnessState == NOT_STRETCHABLE || badnessState == QUITE_LOOSE ||\n      (badnessState == BADNESS_VALUE && ratio > 0);\n}\n\nbool Textblock::BadnessAndPenalty::lineTight ()\n{\n   return\n      badnessState == TOO_TIGHT || (badnessState == BADNESS_VALUE && ratio < 0);\n}\n\nbool Textblock::BadnessAndPenalty::lineTooTight ()\n{\n   return badnessState == TOO_TIGHT;\n}\n\n\nbool Textblock::BadnessAndPenalty::lineMustBeBroken (int penaltyIndex)\n{\n   return penalty[penaltyIndex] == PENALTY_FORCE_BREAK;\n}\n\nbool Textblock::BadnessAndPenalty::lineCanBeBroken (int penaltyIndex)\n{\n   return penalty[penaltyIndex] != PENALTY_PROHIBIT_BREAK;\n}\n\nint Textblock::BadnessAndPenalty::compareTo (int penaltyIndex,\n                                             BadnessAndPenalty *other)\n{\n   for (int l = INF_MAX; l >= 0; l--) {\n      int thisValue = badnessValue (l) + penaltyValue (penaltyIndex, l);\n      int otherValue =\n         other->badnessValue (l) + other->penaltyValue (penaltyIndex, l);\n\n      if (thisValue != otherValue)\n         return thisValue - otherValue;\n   }\n\n   return 0;\n}\n\nvoid Textblock::BadnessAndPenalty::intoStringBuffer(StringBuffer *sb)\n{\n   switch (badnessState) {\n   case NOT_STRETCHABLE:\n      sb->append (\"not stretchable\");\n      break;\n\n   case TOO_TIGHT:\n      sb->append (\"too tight\");\n      break;\n\n   case QUITE_LOOSE:\n      sb->append (\"quite loose (ratio = \");\n      sb->appendInt (ratio);\n      sb->append (\")\");\n      break;\n\n   case BADNESS_VALUE:\n      sb->appendInt (badness);\n      break;\n   }\n\n#ifdef DEBUG\n   sb->append (\" [\");\n   sb->appendInt (totalWidth);\n   sb->append (\" + \");\n   sb->appendInt (totalStretchability);\n   sb->append (\" - \");\n   sb->appendInt (totalShrinkability);\n   sb->append (\" vs. \");\n   sb->appendInt (idealWidth);\n   sb->append (\"]\");\n#endif\n\n   sb->append (\" + (\");\n   for (int i = 0; i < 2; i++) {\n      if (penalty[i] == INT_MIN)\n         sb->append (\"-inf\");\n      else if (penalty[i] == INT_MAX)\n         sb->append (\"inf\");\n      else\n         sb->appendInt (penalty[i]);\n\n      if (i == 0)\n         sb->append (\", \");\n   }\n   sb->append (\")\");\n}\n\n/*\n * ...\n *\n * diff ...\n */\nvoid Textblock::justifyLine (Line *line, int diff)\n{\n   DBG_OBJ_ENTER (\"construct.line\", 0, \"justifyLine\", \"..., %d\", diff);\n\n   // To avoid rounding errors, the calculation is based on accumulated\n   // values. See doc/rounding-errors.doc.\n\n   if (diff > 0) {\n      int spaceStretchabilitySum = 0;\n      for (int i = line->firstWord; i < line->lastWord; i++)\n         spaceStretchabilitySum += getSpaceStretchability(words->getRef(i));\n\n      if (spaceStretchabilitySum > 0) {\n         int spaceStretchabilityCum = 0;\n         int spaceDiffCum = 0;\n         for (int i = line->firstWord; i < line->lastWord; i++) {\n            Word *word = words->getRef (i);\n            spaceStretchabilityCum += getSpaceStretchability(word);\n            int spaceDiff =\n               spaceStretchabilityCum * diff / spaceStretchabilitySum\n               - spaceDiffCum;\n            spaceDiffCum += spaceDiff;\n\n            DBG_OBJ_MSGF (\"construct.line\", 1, \"%d (of %d): diff = %d\",\n                          i, words->size (), spaceDiff);\n\n            word->effSpace = word->origSpace + spaceDiff;\n         }\n      }\n   } else if (diff < 0) {\n      int spaceShrinkabilitySum = 0;\n      for (int i = line->firstWord; i < line->lastWord; i++)\n         spaceShrinkabilitySum += getSpaceShrinkability(words->getRef(i));\n\n      if (spaceShrinkabilitySum > 0) {\n         int spaceShrinkabilityCum = 0;\n         int spaceDiffCum = 0;\n         for (int i = line->firstWord; i < line->lastWord; i++) {\n            Word *word = words->getRef (i);\n            spaceShrinkabilityCum += getSpaceShrinkability(word);\n            int spaceDiff =\n               spaceShrinkabilityCum * diff / spaceShrinkabilitySum\n               - spaceDiffCum;\n            spaceDiffCum += spaceDiff;\n\n            DBG_OBJ_MSGF (\"construct.line\", 1, \"%d (of %d): diff = %d\",\n                          i, words->size (), spaceDiff);\n\n            word->effSpace = word->origSpace + spaceDiff;\n         }\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n\nTextblock::Line *Textblock::addLine (int firstWord, int lastWord,\n                                     int newLastOofPos, bool temporary,\n                                     int minHeight)\n{\n   DBG_OBJ_ENTER (\"construct.line\", 0, \"addLine\", \"%d, %d, %d, %s, %d\",\n                  firstWord, lastWord, newLastOofPos,\n                  temporary ? \"true\" : \"false\", minHeight);\n   DBG_OBJ_MSGF (\"construct.line\", 0, \"=> %d\", lines->size ());\n\n   int lineWidth;\n   if (lastWord >= firstWord) {\n      DBG_MSG_WORD (\"construct.line\", 1, \"<i>first word:</i> \", firstWord, \"\");\n      DBG_MSG_WORD (\"construct.line\", 1, \"<i>last word:</i> \", lastWord, \"\");\n\n      Word *lastWordOfLine = words->getRef(lastWord);\n      // Word::totalWidth includes the hyphen (which is what we want here).\n      lineWidth = lastWordOfLine->totalWidth;\n      DBG_OBJ_MSGF (\"construct.line\", 1, \"lineWidth (from last word): %d\",\n                    lineWidth);\n   } else {\n      // empty line\n      lineWidth = 0;\n      DBG_OBJ_MSGF (\"construct.line\", 1, \"lineWidth (empty line): %d\",\n                    lineWidth);\n   }\n\n   // \"lineWidth\" is relative to leftOffset, so we may have to add\n   // \"line1OffsetEff\" (remember: this is, for list items, negative).\n   if (lines->size () == 0) {\n      lineWidth += line1OffsetEff;\n      DBG_OBJ_MSGF (\"construct.line\", 1, \"lineWidth (line1OffsetEff): %d\",\n                    lineWidth);\n   }\n\n   lines->increase ();\n   DBG_OBJ_SET_NUM (\"lines.size\", lines->size ());\n\n   if(!temporary) {\n      // If the last line was temporary, this will be temporary, too, even\n      // if not requested.\n      if (lines->size () == 1 || nonTemporaryLines == lines->size () - 1)\n         nonTemporaryLines = lines->size ();\n   }\n\n   DBG_OBJ_MSGF (\"construct.line\", 1, \"nonTemporaryLines = %d\",\n                 nonTemporaryLines);\n\n   int lineIndex = lines->size () - 1;\n   Line *line = lines->getRef (lineIndex);\n\n   line->firstWord = firstWord;\n   line->lastWord = lastWord;\n\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"firstWord\", line->firstWord);\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"lastWord\", line->lastWord);\n\n   line->borderAscent = line->contentAscent = 0;\n   line->borderDescent = line->contentDescent = 0;\n   line->marginAscent = 0;\n   line->marginDescent = 0;\n   line->breakSpace = 0;\n\n   bool regardBorder = mustBorderBeRegarded (line);\n   line->leftOffset = max (regardBorder ? newLineLeftBorder : 0,\n                           boxOffsetX () + leftInnerPadding\n                           + (lineIndex == 0 ? line1OffsetEff : 0));\n   line->rightOffset = max (regardBorder ? newLineRightBorder : 0,\n                            boxRestWidth ());\n\n   DBG_OBJ_MSGF (\"construct.line\", 1,\n                 \"regardBorder = %s, newLineLeftBorder = %d, \"\n                 \"newLineRightBorder = %d\",\n                 regardBorder ? \"true\" : \"false\", newLineLeftBorder,\n                 newLineRightBorder);\n\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"leftOffset\", line->leftOffset);\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"rightOffset\",\n                           line->rightOffset);\n\n   alignLine (lineIndex);\n   calcTextOffset (lineIndex, lineBreakWidth);\n\n   for (int i = line->firstWord; i < line->lastWord; i++) {\n      Word *word = words->getRef (i);\n      lineWidth += (word->effSpace - word->origSpace);\n      DBG_OBJ_MSGF (\"construct.line\", 1,\n                    \"lineWidth [corrected space (%d - %d) after word %d]: %d\",\n                    word->effSpace, word->origSpace, i, lineWidth);\n   }\n\n   // Until here, lineWidth refers does not include floats on the left\n   // side. To include left floats, so that maxLineWidth, and\n   // eventually the requisition, is correct, line->leftOffset (minus\n   // margin+border+padding) has to be added, which was calculated\n   // just before. The correction in sizeAllocateImpl() is irrelevant\n   // in this regard. Also, right floats are not regarded here, but in\n   // OutOfFlowMgr::getSize(),\n   lineWidth += (line->leftOffset - getStyle()->boxOffsetX ());\n\n   if (lines->size () == 1) {\n      // first line\n      line->maxLineWidth = lineWidth;\n      line->lastOofRefPositionedBeforeThisLine = -1;\n   } else {\n      Line *prevLine = lines->getRef (lines->size () - 2);\n      line->maxLineWidth = max (lineWidth, prevLine->maxLineWidth);\n      line->lastOofRefPositionedBeforeThisLine =\n         prevLine->lastOofRefPositionedBeforeThisLine;\n   }\n\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"maxLineWidth\",\n                           line->maxLineWidth);\n\n   for(int i = line->firstWord; i <= line->lastWord; i++)\n      accumulateWordForLine (lineIndex, i);\n\n   if (lines->size () == 1)\n      line->top = 0;\n   else {\n      // See comment in Line::totalHeight for collapsing of the\n      // margins of adjacent lines.\n      Line *prevLine = lines->getRef (lines->size () - 2);\n      line->top = prevLine->top\n         + prevLine->totalHeight (line->marginAscent - line->borderAscent);\n   }\n\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"top\", line->top);\n\n   // Especially empty lines (possible when there are floats) have\n   // zero height, which may cause endless loops. For this reasons,\n   // the height should be positive (assuming the caller passed\n   // minHeight > 0).\n   line->borderAscent = max (line->borderAscent, minHeight);\n   line->marginAscent = max (line->marginAscent, minHeight);\n\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"borderAscent\",\n                           line->borderAscent);\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"borderDescent\",\n                           line->borderDescent);\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"marginAscent\",\n                           line->marginAscent);\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"marginDescent\",\n                           line->marginDescent);\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"contentAscent\",\n                           line->contentAscent);\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"contentDescent\",\n                           line->contentDescent);\n\n   mustQueueResize = true;\n   DBG_OBJ_SET_BOOL (\"mustQueueResize\", mustQueueResize);\n\n   int xWidget = line->textOffset;\n   int yLine = yOffsetOfLineCreated (line);\n   for (int i = firstWord; i <= lastWord; i++) {\n      Word *word = words->getRef (i);\n      if (word->wordImgRenderer)\n         word->wordImgRenderer->setData (xWidget, lines->size () - 1);\n      if (word->spaceImgRenderer)\n         word->spaceImgRenderer->setData (xWidget, lines->size () - 1);\n\n      if (word->content.type == core::Content::WIDGET_OOF_REF) {\n         Widget *widget = word->content.widgetReference->widget;\n         int oofmIndex = getWidgetOOFIndex (widget);\n         oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex);\n         // See also Textblock::sizeAllocate, and notes there about\n         // vertical alignment. Calculating the vertical position\n         // should probably be centralized.\n         if (oofm) {\n            int xRel = xWidget;\n            int yRel = yLine + (line->borderAscent - word->size.ascent);\n            int xRef, yRef;\n            if (findSizeRequestReference (oofmIndex, &xRef, &yRef))\n               oofm->tellPosition2 (widget, xRef + xRel, yRef + yRel);\n            else\n               oofm->tellIncompletePosition2 (widget, this, xRel, yRel);\n         }\n      }\n\n      xWidget += word->size.width + word->effSpace;\n   }\n\n   line->lastOofRefPositionedBeforeThisLine =\n      max (line->lastOofRefPositionedBeforeThisLine, newLastOofPos);\n   DBG_OBJ_SET_NUM (\"lastLine.lastOofRefPositionedBeforeThisLine\",\n                    line->lastOofRefPositionedBeforeThisLine);\n\n   initNewLine ();\n\n   DBG_OBJ_LEAVE ();\n   return line;\n}\n\nvoid Textblock::processWord (int wordIndex)\n{\n   DBG_OBJ_ENTER (\"construct.all\", 0, \"processWord\", \"%d\", wordIndex);\n   DBG_MSG_WORD (\"construct.all\", 1, \"<i>processed word:</i>\", wordIndex, \"\");\n\n   int diffWords = wordWrap (wordIndex, false);\n\n   if (diffWords == 0)\n      handleWordExtremes (wordIndex);\n   else {\n      // If wordWrap has called hyphenateWord here, this has an effect\n      // on the call of handleWordExtremes. To avoid adding values\n      // more than one time (original un-hyphenated word, plus all\n      // parts of the hyphenated word, except the first one), the\n      // whole paragraph is recalculated again.\n      //\n      // (Note: the hyphenated word is often *before* wordIndex, and\n      // it may be even more than one word, which makes it nearly\n      // impossible to reconstruct what has happend. Therefore, there\n      // is no simpler approach to handle this.)\n\n      DBG_OBJ_MSGF (\"construct.paragraph\", 1,\n                    \"word list has become longer by %d\", diffWords);\n      DBG_MSG_WORD (\"construct.all\", 1, \"<i>processed word now:</i>\",\n                    wordIndex, \"\");\n\n      int firstWord;\n      if (paragraphs->size() > 0) {\n         firstWord = paragraphs->getLastRef()->firstWord;\n         paragraphs->setSize (paragraphs->size() - 1);\n         DBG_OBJ_SET_NUM (\"paragraphs.size\", paragraphs->size ());\n         DBG_OBJ_MSG (\"construct.paragraph\", 1, \"removing last paragraph\");\n      } else\n         firstWord = 0;\n\n      int lastIndex = wordIndex + diffWords;\n      DBG_OBJ_MSGF (\"construct.paragraph\", 1,\n                    \"processing words again from %d to %d\",\n                    firstWord, lastIndex);\n\n      // Furthermore, some more words have to be processed, so we\n      // iterate until wordIndex + diffWords, not only\n      // wordIndex.\n      DBG_OBJ_MSG_START ();\n      for (int i = firstWord; i <= lastIndex; i++)\n         handleWordExtremes (i);\n      DBG_OBJ_MSG_END ();\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n/*\n * This method is called in two cases: (i) when a word is added\n * (ii) when a page has to be (partially) rewrapped. It does word wrap,\n * and adds new lines if necessary.\n *\n * Returns whether the words list has changed at, or before, the word\n * index.\n */\nint Textblock::wordWrap (int wordIndex, bool wrapAll)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"wordWrap\", \"%d, %s\",\n                 wordIndex, wrapAll ? \"true\" : \"false\");\n   DBG_MSG_WORD (\"construct.word\", 1, \"<i>wrapped word:</i> \", wordIndex, \"\");\n\n   if (!wrapAll)\n      removeTemporaryLines ();\n\n   initLine1Offset (wordIndex);\n\n   Word *word = words->getRef (wordIndex);\n   word->effSpace = word->origSpace;\n\n   accumulateWordData (wordIndex);\n\n   int n;\n   if (word->content.type == core::Content::WIDGET_OOF_REF)\n      n = wrapWordOofRef (wordIndex, wrapAll);\n   else\n      n = wrapWordInFlow (wordIndex, wrapAll);\n\n   DBG_OBJ_MSGF (\"construct.word\", 1, \"=> %d\", n);\n   DBG_OBJ_LEAVE ();\n\n   return n;\n}\n\nint Textblock::wrapWordInFlow (int wordIndex, bool wrapAll)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"wrapWordInFlow\", \"%d, %s\",\n                 wordIndex, wrapAll ? \"true\" : \"false\");\n\n   Word *word = words->getRef (wordIndex);\n   int diffWords = 0;\n\n   int penaltyIndex = calcPenaltyIndexForNewLine ();\n\n   bool newLine;\n   do {\n      // This variable, thereWillBeMoreSpace, is set to true, if, due\n      // to floats, this line is smaller than following lines will be\n      // (and, at the end, there will be surely lines without\n      // floats). If this is the case, lines may, in an extreme case,\n      // be left empty.\n\n      // (In other cases, lines are never left empty, even if this means\n      // that the contents is wider than the line break width. Leaving\n      // lines empty does not make sense without floats, since there will\n      // be no possibility with more space anymore.)\n\n      bool regardBorder =  mustBorderBeRegarded (lines->size ());\n      bool thereWillBeMoreSpace = regardBorder ?\n         newLineHasFloatLeft || newLineHasFloatRight : false;\n\n      DBG_OBJ_MSGF (\"construct.word\", 1,\n                    \"thereWillBeMoreSpace = %s ? %s || %s : false = %s\",\n                    regardBorder ? \"true\" : \"false\",\n                    newLineHasFloatLeft ? \"true\" : \"false\",\n                    newLineHasFloatRight ? \"true\" : \"false\",\n                    thereWillBeMoreSpace ? \"true\" : \"false\");\n\n\n      bool tempNewLine = false;\n      int firstIndex =\n         lines->size() == 0 ? 0 : lines->getLastRef()->lastWord + 1;\n      int searchUntil;\n\n      if (wordIndex < firstIndex)\n          // Current word is already part of a line (ending with\n          // firstIndex - 1), so no new line has to be added.\n          newLine = false;\n      else if (wrapAll && wordIndex >= firstIndex &&\n               wordIndex == words->size() -1) {\n         newLine = true;\n         searchUntil = wordIndex;\n         tempNewLine = true;\n         DBG_OBJ_MSG (\"construct.word\", 1, \"<b>new line:</b> last word\");\n      } else if (wordIndex >= firstIndex &&\n                 // TODO: lineMustBeBroken should be independent of\n                 // the penalty index?\n                 word->badnessAndPenalty.lineMustBeBroken (penaltyIndex)) {\n         newLine = true;\n         searchUntil = wordIndex;\n         DBG_OBJ_MSG (\"construct.word\", 1, \"<b>new line:</b> forced break\");\n      } else {\n         // Break the line when too tight, but only when there is a\n         // possible break point so far. (TODO: I've forgotten the\n         // original bug which is fixed by this.)\n\n         // Exception of the latter rule: thereWillBeMoreSpace; see\n         // above, where it is defined.\n\n         DBG_OBJ_MSGF (\"construct.word\", 1,\n                       \"possible line break between %d and %d?\",\n                       firstIndex, wordIndex - 1);\n         DBG_OBJ_MSG_START ();\n\n         bool possibleLineBreak = false;\n         for (int i = firstIndex;\n              !(thereWillBeMoreSpace || possibleLineBreak)\n                 && i <= wordIndex - 1;\n              i++) {\n            DBG_OBJ_MSGF (\"construct.word\", 2, \"examining word %d\", i);\n            if (words->getRef(i)->badnessAndPenalty\n                .lineCanBeBroken (penaltyIndex)) {\n               DBG_MSG_WORD (\"construct.word\", 2, \"break possible for word:\",\n                             i, \"\");\n               possibleLineBreak = true;\n            }\n         }\n\n         DBG_OBJ_MSG_END ();\n         DBG_OBJ_MSGF (\"construct.word\", 1, \"=> %s\",\n                       possibleLineBreak ? \"true\" : \"false\");\n\n         DBG_OBJ_MSGF (\"construct.word\", 1, \"word->... too tight: %s\",\n                       word->badnessAndPenalty.lineTooTight () ?\n                       \"true\" : \"false\");\n\n         if ((thereWillBeMoreSpace || possibleLineBreak)\n             && word->badnessAndPenalty.lineTooTight ()) {\n            newLine = true;\n            searchUntil = wordIndex - 1;\n            DBG_OBJ_MSG (\"construct.word\", 1,\n                         \"<b>new line:</b> line too tight\");\n         } else {\n            DBG_OBJ_MSG (\"construct.word\", 1, \"no <b>new line</b>\");\n            newLine = false;\n         }\n      }\n\n      if(!newLine && !wrapAll) {\n         // No new line is added. \"mustQueueResize\" must,\n         // nevertheless, be set, so that flush() will call\n         // queueResize(), and later sizeRequestImpl() is called,\n         // which then calls showMissingLines(), which eventually\n         // calls this method again, with wrapAll == true, so that\n         // newLine is calculated as \"true\".\n         mustQueueResize = true;\n         DBG_OBJ_SET_BOOL (\"mustQueueResize\", mustQueueResize);\n      }\n\n      PRINTF (\"[%p] special case? newLine = %s, wrapAll = %s => \"\n              \"mustQueueResize = %s\\n\", this, newLine ? \"true\" : \"false\",\n              wrapAll ? \"true\" : \"false\", mustQueueResize ? \"true\" : \"false\");\n\n      if (newLine) {\n         accumulateWordData (wordIndex);\n\n         int wordIndexEnd = wordIndex;\n         int height = 1; // assumed by calcBorders before (see there)\n         int breakPos;\n         int lastFloatPos = lines->size() > 0 ?\n            lines->getLastRef()->lastOofRefPositionedBeforeThisLine : -1;\n         DBG_OBJ_MSGF (\"construct.word\", 2, \"lastFloatPos = %d\", lastFloatPos);\n\n         balanceBreakPosAndHeight (wordIndex, firstIndex, &searchUntil,\n                                   tempNewLine, penaltyIndex, true,\n                                   &thereWillBeMoreSpace, wrapAll,\n                                   &diffWords, &wordIndexEnd,\n                                   &lastFloatPos, regardBorder, &height,\n                                   &breakPos);\n\n         bool floatHandled;\n         int yNewLine = yOffsetOfLineToBeCreated ();\n\n         do {\n            DBG_OBJ_MSG (\"construct.word\", 1, \"<i>floatHandled loop cycle</i>\");\n            DBG_OBJ_MSG_START ();\n\n            DBG_OBJ_MSGF (\"construct.word\", 2,\n                          \"breakPos = %d, height = %d, lastFloatPos = %d\",\n                          breakPos, height, lastFloatPos);\n\n            int startSearch = max (firstIndex, lastFloatPos + 1);\n            int newFloatPos = -1;\n\n            // Step 1: search for the next float.\n            DBG_OBJ_MSGF (\"construct.word\", 2, \"searching from %d to %d\",\n                          startSearch, breakPos);\n            for (int i = startSearch; newFloatPos == -1 && i <= breakPos; i++) {\n               core::Content *content = &(words->getRef(i)->content);\n               if (content->type == core::Content::WIDGET_OOF_REF) {\n                  for (int j = 0; newFloatPos == -1 && j < NUM_OOFM; j++) {\n                     Widget *widget = content->widgetReference->widget;\n                     if ((searchOutOfFlowMgr(j)->affectsLeftBorder(widget) ||\n                          searchOutOfFlowMgr(j)->affectsRightBorder (widget)))\n                        newFloatPos = i;\n                  }\n               }\n            }\n\n            DBG_OBJ_MSGF (\"construct.word\", 2, \"newFloatPos = %d\", newFloatPos);\n\n            if (newFloatPos == -1)\n               floatHandled = false;\n            else {\n               floatHandled = true;\n\n               // Step 2: position the float and re-calculate the line.\n\n               // TODO \"x\" is not quite correct, but this does not matter\n               // (currently?).\n\n               lastFloatPos = newFloatPos;\n               \n               Widget *widget =\n                  words->getRef(lastFloatPos)->content.widgetReference->widget;\n               int oofmIndex = getWidgetOOFIndex (widget);\n               oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex);\n               if (oofm && oofm->mayAffectBordersAtAll ()) {\n                  int xRel = boxOffsetX (), yRel = yNewLine, xRef, yRef;\n                  if (findSizeRequestReference (oofmIndex, &xRef, &yRef))\n                     oofm->tellPosition1 (widget, xRef + xRel, yRef + yRel);\n                  else\n                     oofm->tellIncompletePosition1 (widget, this, xRel, yRel);\n               }\n\n               balanceBreakPosAndHeight (wordIndex, firstIndex, &searchUntil,\n                                         tempNewLine, penaltyIndex, false,\n                                         &thereWillBeMoreSpace, wrapAll,\n                                         &diffWords, &wordIndexEnd,\n                                         &lastFloatPos, regardBorder, &height,\n                                         &breakPos);\n            }\n\n            DBG_OBJ_MSG_END ();\n         } while (floatHandled);\n\n         int minHeight;\n         if (firstIndex <= breakPos) {\n            // Not an empty line: calculate line height from contents.\n            minHeight = 1;\n            DBG_OBJ_MSGF (\"construct.word\", 1, \"%d <= %d => minHeight = %d\",\n                          firstIndex, breakPos, minHeight);\n         } else {\n            // Empty line. Too avoid too many lines one pixel high, we\n            // use the float heights.\n            if (newLineHasFloatLeft && newLineHasFloatRight)\n               minHeight = max (min (newLineLeftFloatHeight,\n                                     newLineRightFloatHeight),\n                                1);\n            else if (newLineHasFloatLeft && !newLineHasFloatRight)\n               minHeight = max (newLineLeftFloatHeight, 1);\n            else if (!newLineHasFloatLeft && newLineHasFloatRight)\n               minHeight = max (newLineRightFloatHeight, 1);\n            else\n               // May this happen?\n               minHeight = 1;\n\n            DBG_OBJ_MSGF (\"construct.word\", 1,\n                          \"%d < %d => minHeight = %d (l: %s (%d), r: %s (%d))\",\n                          firstIndex, breakPos, minHeight,\n                          boolToStr (newLineHasFloatLeft),\n                          newLineHasFloatLeft ? newLineLeftFloatHeight : 0,\n                          boolToStr (newLineHasFloatRight),\n                          newLineHasFloatRight ? newLineRightFloatHeight : 0);\n         }\n\n         addLine (firstIndex, breakPos, lastFloatPos, tempNewLine, minHeight);\n\n         DBG_OBJ_MSGF (\"construct.word\", 1,\n                       \"accumulating again from %d to %d\",\n                       breakPos + 1, wordIndexEnd);\n         for(int i = breakPos + 1; i <= wordIndexEnd; i++)\n            accumulateWordData (i);\n\n         // update word pointer as hyphenateWord() can trigger a\n         // reorganization of the words structure\n         word = words->getRef (wordIndex);\n\n         penaltyIndex = calcPenaltyIndexForNewLine ();\n      }\n   } while (newLine);\n\n   if(word->content.type == core::Content::WIDGET_IN_FLOW) {\n      // Set parentRef for the child, when necessary.\n      //\n      // parentRef is set for the child already, when a line is\n      // added. There are a couple of different situations to\n      // consider, e.g. when called from showMissingLines(), this word\n      // may already have been added in a previous call. To make\n      // things simple, we just check whether this word is contained\n      // within any line, or still \"missing\".\n\n      int firstWordWithoutLine;\n      if (lines->size() == 0)\n         firstWordWithoutLine = 0;\n      else\n         firstWordWithoutLine = lines->getLastRef()->lastWord + 1;\n\n      if (wordIndex >= firstWordWithoutLine) {\n         word->content.widget->parentRef = makeParentRefInFlow (lines->size ());\n         DBG_OBJ_SET_NUM_O (word->content.widget, \"parentRef\",\n                            word->content.widget->parentRef);\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n\n   return diffWords;\n}\n\nint Textblock::wrapWordOofRef (int wordIndex, bool wrapAll)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"wrapWordOofRef\", \"%d, %s\",\n                  wordIndex, wrapAll ? \"true\" : \"false\");\n\n   Word *word = words->getRef (wordIndex);\n   Widget *widget = word->content.widgetReference->widget;\n   int yNewLine = yOffsetOfLineToBeCreated ();\n\n   // Floats, which affect either border, are handled in wrapWordInFlow; this\n   // is rather for positioned elements (but only for completeness:\n   // tellPosition1 is not implemented for positioned elements).\n   int oofmIndex = getWidgetOOFIndex (widget);\n   oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex);\n   DBG_OBJ_MSGF (\"construct.word\", 1, \"parentRef = %d, oofm = %p\",\n                 widget->parentRef, oofm);\n   if (oofm && !oofm->mayAffectBordersAtAll ()) {\n      // TODO Again, \"x\" is not correct (see above).\n      int xRel = boxOffsetX (), yRel = yNewLine, xRef, yRef;\n      if (findSizeRequestReference (oofmIndex, &xRef, &yRef))\n         oofm->tellPosition1 (widget, xRef + xRel, yRef + yRel);\n      else\n         oofm->tellIncompletePosition1 (widget, this, xRel, yRel);\n   }\n\n   if(word->content.type == core::Content::WIDGET_OOF_REF) {\n      // Set parentRef for the referred widget. Cf. wrapWordInFlow.\n      int firstWordWithoutLine;\n      if (lines->size() == 0)\n         firstWordWithoutLine = 0;\n      else\n         firstWordWithoutLine = lines->getLastRef()->lastWord + 1;\n\n      if (wordIndex >= firstWordWithoutLine) {\n         word->content.widgetReference->parentRef =\n            makeParentRefInFlow (lines->size ());\n         DBG_SET_WORD (wordIndex);\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n\n   return 0; // Words list not changed.\n}\n\n// *height must be initialized, but not *breakPos.\n// *wordIndexEnd must be initialized (initially to wordIndex)\nvoid Textblock::balanceBreakPosAndHeight (int wordIndex, int firstIndex,\n                                          int *searchUntil, bool tempNewLine,\n                                          int penaltyIndex,\n                                          bool borderIsCalculated,\n                                          bool *thereWillBeMoreSpace,\n                                          bool wrapAll, int *diffWords,\n                                          int *wordIndexEnd, int *lastFloatPos,\n                                          bool regardBorder, int *height,\n                                          int *breakPos)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"balanceBreakPosAndHeight\",\n                  \"%d, %d. %d, %s, %d, %s, ..., %s, ..., %d, %s, %d, ...\",\n                  wordIndex, firstIndex, *searchUntil,\n                  tempNewLine ? \"true\" : \"false\", penaltyIndex,\n                  borderIsCalculated ? \"true\" : \"false\",\n                  wrapAll ? \"true\" : \"false\", *lastFloatPos,\n                  regardBorder ? \"true\" : \"false\", *height);\n\n   // The height of this part of the line (until the new break\n   // position) may change with the break position, but the break\n   // position may depend on the height. We try to let these values\n   // converge.\n   //\n   // The height, as a function of the break position, is\n   // monotonically (but not strictly) increasing, since more words\n   // may make the line higher (but not flatter). The break position,\n   // as a function of the height, is, however, monotonically (but not\n   // strictly) *de*creasing, since flatter lines may fit easier\n   // between floats (although this is a rare case). So a convergence\n   // is not necessary.\n   //\n   // For this reason, we iterate only as long as the height does not\n   // increase again, and stop if it remains the same. As the minimum\n   // is 1, this approach will force the iteration to stop.\n   //\n   // (As a side effect, this will lead to a larger break position,\n   // and so place as much words as possible in the line.)\n\n   int runNo = 1;\n   while (true) {\n      if (!(borderIsCalculated && runNo == 1)) {\n         // borderIsCalculated is, of course, only valid in the first run\n         calcBorders (*lastFloatPos, *height);\n         *thereWillBeMoreSpace = regardBorder ?\n            newLineHasFloatLeft || newLineHasFloatRight : false;\n\n         for(int i = firstIndex; i <= *wordIndexEnd; i++)\n            accumulateWordData (i);\n      }\n\n      DBG_OBJ_MSGF (\"construct.word\", 1, \"thereWillBeMoreSpace = %s\",\n                    *thereWillBeMoreSpace ? \"true\" : \"false\");\n\n      int newBreakPos =\n         searchBreakPos (wordIndex, firstIndex, searchUntil,\n                         tempNewLine, penaltyIndex, *thereWillBeMoreSpace,\n                         wrapAll, diffWords, wordIndexEnd, lastFloatPos);\n      int newHeight = calcLinePartHeight (firstIndex, newBreakPos);\n\n      DBG_OBJ_MSGF (\"construct.word\", 1,\n                    \"runNo = %d, newBreakPos = %d, newHeight = %d\",\n                    runNo, newBreakPos, newHeight);\n      if (runNo == 1)\n         DBG_OBJ_MSGF (\"construct.word\", 1,\n                       \"old: height = %d, breakPos undefined\", *height);\n      else\n         DBG_OBJ_MSGF (\"construct.word\", 1,\n                       \"old: height = %d, breakPos = %d\", *height, *breakPos);\n\n      if (runNo != 1 /* Since *some* value are needed, the results\n                        from the first run are never discarded. */\n          && newHeight >= *height) {\n         if (newHeight == *height) {\n            // newHeight == height: convergence, stop here. The new break\n            // position is, nevertheless, adopted.\n            DBG_OBJ_MSG (\"construct.word\", 1, \"stopping, adopting new values\");\n            *breakPos = newBreakPos;\n         } else\n            // newHeight > height: do not proceed, discard new values,\n            // which are less desirable than the old ones (see above).\n            DBG_OBJ_MSG (\"construct.word\", 1,\n                         \"stopping, discarding new values\");\n         break;\n      } else {\n         DBG_OBJ_MSG (\"construct.word\", 1, \"adopting new values, continuing\");\n         *height = newHeight;\n         *breakPos = newBreakPos;\n      }\n\n      runNo++;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d, %d, %d, %d\",\n                      *searchUntil, *lastFloatPos, *height, *breakPos);\n}\n\n// *wordIndexEnd must be initialized (initially to wordIndex)\nint Textblock::searchBreakPos (int wordIndex, int firstIndex, int *searchUntil,\n                               bool tempNewLine, int penaltyIndex,\n                               bool thereWillBeMoreSpace, bool wrapAll,\n                               int *diffWords, int *wordIndexEnd,\n                               int *addIndex1)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"searchBreakPos\",\n                  \"%d, %d. %d, %s, %d, %s, %s, ...\",\n                  wordIndex, firstIndex, *searchUntil,\n                  tempNewLine ? \"true\" : \"false\", penaltyIndex,\n                  thereWillBeMoreSpace ? \"true\" : \"false\",\n                  wrapAll ? \"true\" : \"false\");\n\n   DBG_MSG_WORD (\"construct.word\", 1, \"<i>first word:</i> \", firstIndex, \"\");\n\n   int result;\n   bool lineAdded;\n\n   do {\n      DBG_OBJ_MSG (\"construct.word\", 1, \"<i>searchBreakPos loop cycle</i>\");\n      DBG_OBJ_MSG_START ();\n\n      if (firstIndex > *searchUntil) {\n         // empty line\n         DBG_OBJ_MSG (\"construct.word\", 1, \"empty line\");\n         assert (*searchUntil == firstIndex - 1);\n         result = firstIndex - 1;\n         lineAdded = true;\n      } else if (thereWillBeMoreSpace &&\n                 words->getRef(firstIndex)->badnessAndPenalty.lineTooTight ()) {\n         int hyphenatedWord = considerHyphenation (firstIndex, firstIndex);\n\n         DBG_IF_RTFL {\n            StringBuffer sb;\n            words->getRef(firstIndex)->badnessAndPenalty.intoStringBuffer (&sb);\n            DBG_OBJ_MSGF (\"construct.word\", 1,\n                          \"too tight: %s ... hyphenatedWord = %d\",\n                          sb.getChars (), hyphenatedWord);\n         }\n\n         if (hyphenatedWord == -1) {\n            DBG_OBJ_MSG (\"construct.word\", 1, \"... => empty line\");\n            result = firstIndex - 1;\n            lineAdded = true;\n         } else {\n            DBG_OBJ_MSG (\"construct.word\", 1,\n                         \"... => hyphenate word and try again\");\n            int n = hyphenateWord (hyphenatedWord, addIndex1);\n            *searchUntil += n;\n            if (hyphenatedWord <= wordIndex)\n               *wordIndexEnd += n;\n            DBG_OBJ_MSGF (\"construct.word\", 1, \"new searchUntil = %d\",\n                          *searchUntil);\n\n            lineAdded = false;\n         }\n      } else {\n         DBG_OBJ_MSG (\"construct.word\", 1, \"non-empty line\");\n\n         int breakPos =\n            searchMinBap (firstIndex, *searchUntil, penaltyIndex,\n                          thereWillBeMoreSpace, wrapAll);\n         int hyphenatedWord = considerHyphenation (firstIndex, breakPos);\n\n         DBG_OBJ_MSGF (\"construct.word\", 1, \"breakPos = %d\", breakPos);\n         DBG_MSG_WORD (\"construct.word\", 1, \"<i>break at word:</i> \",\n                       breakPos, \"\");\n         DBG_OBJ_MSGF (\"construct.word\", 1, \"hyphenatedWord = %d\",\n                       hyphenatedWord);\n         if (hyphenatedWord != -1)\n            DBG_MSG_WORD (\"construct.word\", 1,\n                          \"<i>hyphenate at word:</i> \",\n                          hyphenatedWord, \"\");\n\n         if(hyphenatedWord == -1) {\n            result = breakPos;\n            lineAdded = true;\n         } else {\n            // TODO hyphenateWord() should return whether something\n            // has changed at all. So that a second run, with\n            // !word->canBeHyphenated, is unnecessary.\n            // TODO Update: The return value of hyphenateWord() should\n            // be checked.\n            DBG_OBJ_MSGF (\"construct.word\", 1, \"old searchUntil = %d\",\n                          *searchUntil);\n            int n = hyphenateWord (hyphenatedWord, addIndex1);\n            *searchUntil += n;\n            if (hyphenatedWord <= wordIndex)\n               *wordIndexEnd += n;\n            DBG_OBJ_MSGF (\"construct.word\", 1, \"new searchUntil = %d\",\n                          *searchUntil);\n            lineAdded = false;\n\n            if (hyphenatedWord <= wordIndex)\n               *diffWords += n;\n\n            DBG_OBJ_MSGF (\"construct.word\", 1,\n                          \"accumulating again from %d to %d\",\n                          breakPos + 1, *wordIndexEnd);\n            for(int i = breakPos + 1; i <= *wordIndexEnd; i++)\n               accumulateWordData (i);\n         }\n      }\n\n      DBG_OBJ_MSG_END ();\n   } while(!lineAdded);\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", result);\n\n   return result;\n}\n\nint Textblock::searchMinBap (int firstWord, int lastWord, int penaltyIndex,\n                             bool thereWillBeMoreSpace, bool correctAtEnd)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"searchMinBap\", \"%d, %d, %d, %s, %s\",\n                  firstWord, lastWord, penaltyIndex,\n                  thereWillBeMoreSpace ? \"true\" : \"false\",\n                  correctAtEnd ? \"true\" : \"false\");\n\n   int pos = -1;\n\n   DBG_OBJ_MSG_START ();\n   for (int i = firstWord; i <= lastWord; i++) {\n      Word *w = words->getRef(i);\n\n      DBG_IF_RTFL {\n         StringBuffer sb;\n         w->badnessAndPenalty.intoStringBuffer (&sb);\n         DBG_OBJ_MSGF (\"construct.word\", 2, \"%d (of %d): b+p: %s\",\n                       i, words->size (), sb.getChars ());\n         DBG_MSG_WORD (\"construct.word\", 2, \"(<i>i. e.:</i> \", i, \")\");\n      }\n\n      // \"<=\" instead of \"<\" in the next lines (see also\n      // \"correctedBap.compareTo ...) tends to result in more words\n      // per line -- theoretically. Practically, the case \"==\" will\n      // never occur.\n      if (pos == -1 ||\n          w->badnessAndPenalty.compareTo (penaltyIndex,\n                                          &words->getRef(pos)\n                                          ->badnessAndPenalty) <= 0)\n         pos = i;\n   }\n   DBG_OBJ_MSG_END ();\n\n   DBG_OBJ_MSGF (\"construct.word\", 1, \"found at %d\", pos);\n\n   if (correctAtEnd && lastWord == words->size () - 1) {\n      // Since no break and no space is added, the last word will have\n      // a penalty of inf. Actually, it should be less, since it is\n      // the last word. However, since more words may follow, the\n      // penalty is not changed, but here, the search is corrected\n      // (maybe only temporary).\n\n      // (Notice that it was once (temporally) set to -inf, not 0, but\n      // this will make e.g. test/table-1.html not work.)\n      Word *w = words->getRef (lastWord);\n      BadnessAndPenalty correctedBap = w->badnessAndPenalty;\n      correctedBap.setPenalty (0);\n\n      DBG_IF_RTFL {\n         StringBuffer sb;\n         correctedBap.intoStringBuffer (&sb);\n         DBG_OBJ_MSGF (\"construct.word\", 1, \"corrected b+p: %s\",\n                       sb.getChars ());\n      }\n\n      if (correctedBap.compareTo(penaltyIndex,\n                                 &words->getRef(pos)->badnessAndPenalty) <= 0) {\n         pos = lastWord;\n         DBG_OBJ_MSGF (\"construct.word\", 1, \"corrected: %d\", pos);\n      }\n   }\n\n   DBG_OBJ_LEAVE ();\n   return pos;\n}\n\n/**\n * Suggest a word to hyphenate, when breaking at breakPos is\n * planned. Return a word index or -1, when hyphenation makes no\n * sense.\n */\nint Textblock::considerHyphenation (int firstIndex, int breakPos)\n{\n   int hyphenatedWord = -1;\n\n   Word *wordBreak = words->getRef(breakPos);\n   //printf (\"[%p] line (broken at word %d): \", this, breakPos);\n   //printWord (wordBreak);\n   //printf (\"\\n\");\n\n   // A tight line: maybe, after hyphenation, some parts of the last\n   // word of this line can be put into the next line.\n   if (wordBreak->badnessAndPenalty.lineTight ()) {\n      // Sometimes, it is not the last word, which must be hyphenated,\n      // but some word before. Here, we search for the first word\n      // which can be hyphenated, *and* makes the line too tight.\n      for (int i = breakPos; i >= firstIndex; i--) {\n         Word *word1 = words->getRef (i);\n         if (word1->badnessAndPenalty.lineTight () &&\n             isHyphenationCandidate (word1))\n            hyphenatedWord = i;\n      }\n   }\n\n   // A loose line: maybe, after hyphenation, some parts of the first\n   // word of the next line can be put into this line.\n   if (wordBreak->badnessAndPenalty.lineLoose () &&\n       breakPos + 1 < words->size ()) {\n      Word *word2 = words->getRef(breakPos + 1);\n      if (isHyphenationCandidate (word2))\n         hyphenatedWord = breakPos + 1;\n   }\n\n   return hyphenatedWord;\n}\n\nbool Textblock::isHyphenationCandidate (Word *word)\n{\n   return (word->flags & Word::CAN_BE_HYPHENATED) &&\n      word->style->x_lang[0] &&\n      isBreakAllowedInWord (word) &&\n      word->content.type == core::Content::TEXT &&\n      Hyphenator::isHyphenationCandidate (word->content.text);\n}\n\n\nint Textblock::calcLinePartHeight (int firstWord, int lastWord)\n{\n   int ascent = 0, descent = 0;\n\n   for (int i = firstWord; i <= lastWord; i++) {\n      Word *word = words->getRef (i);\n      ascent = max (ascent, word->size.ascent);\n      descent = max (descent, word->size.descent);\n   }\n\n   return max (ascent + descent, 1);\n}\n\n/**\n * Counter part to wordWrap(), but for extremes, not size calculation.\n */\nvoid Textblock::handleWordExtremes (int wordIndex)\n{\n   // TODO Overall, clarify penalty index.\n\n   DBG_OBJ_ENTER (\"construct.paragraph\", 0, \"handleWordExtremes\", \"%d\",\n                  wordIndex);\n\n   initLine1Offset (wordIndex);\n\n   Word *word = words->getRef (wordIndex);\n   DBG_MSG_WORD (\"construct.paragraph\", 1,\n                 \"<i>handled word:</i> \", wordIndex, \"\");\n\n   core::Extremes wordExtremes;\n   getWordExtremes (word, &wordExtremes);\n   DBG_OBJ_MSGF (\"construct.paragraph\", 1, \"extremes: %d (%d) / %d (%d)\",\n                 wordExtremes.minWidth, wordExtremes.minWidthIntrinsic,\n                 wordExtremes.maxWidth, wordExtremes.maxWidthIntrinsic);\n\n   if (wordIndex == 0) {\n      wordExtremes.minWidth += line1OffsetEff;\n      wordExtremes.minWidthIntrinsic += line1OffsetEff;\n      wordExtremes.maxWidth += line1OffsetEff;\n      wordExtremes.maxWidthIntrinsic += line1OffsetEff;\n   }\n\n   if (paragraphs->size() == 0 ||\n       words->getRef(paragraphs->getLastRef()->lastWord)\n       ->badnessAndPenalty.lineMustBeBroken (1)) {\n      // Add a new paragraph.\n      paragraphs->increase ();\n      DBG_OBJ_SET_NUM (\"paragraphs.size\", paragraphs->size ());\n\n      Paragraph *prevPar = paragraphs->size() == 1 ?\n         NULL : paragraphs->getRef(paragraphs->size() - 2);\n      Paragraph *par = paragraphs->getLastRef();\n\n      par->firstWord = par->lastWord = wordIndex;\n      par->parMin = par->parMinIntrinsic = par->parMax = par->parMaxIntrinsic =\n         par->parAdjustmentWidth = 0;\n\n      if (prevPar) {\n         par->maxParMin = prevPar->maxParMin;\n         par->maxParMinIntrinsic = prevPar->maxParMinIntrinsic;\n         par->maxParMax = prevPar->maxParMax;\n         par->maxParMaxIntrinsic = prevPar->maxParMaxIntrinsic;\n         par->maxParAdjustmentWidth = prevPar->maxParAdjustmentWidth;\n      } else\n         par->maxParMin = par->maxParMinIntrinsic = par->maxParMax =\n            par->maxParMaxIntrinsic = par->maxParAdjustmentWidth = 0;\n\n      DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1, \"maxParMin\",\n                              par->maxParMin);\n      DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                              \"maxParMinIntrinsic\", par->maxParMinIntrinsic);\n      DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1, \"maxParMax\",\n                              par->maxParMax);\n      DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                              \"maxParMaxIntrinsic\", par->maxParMaxIntrinsic);\n   }\n\n   Paragraph *lastPar = paragraphs->getLastRef();\n\n   int corrDiffMin, corrDiffMax;\n   if (wordIndex - 1 >= lastPar->firstWord) {\n      Word *lastWord = words->getRef (wordIndex - 1);\n      if (lastWord->badnessAndPenalty.lineCanBeBroken (1) &&\n          (lastWord->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0)\n         corrDiffMin = 0;\n      else\n         corrDiffMin = lastWord->origSpace - lastWord->hyphenWidth;\n\n      corrDiffMax = lastWord->origSpace - lastWord->hyphenWidth;\n   } else\n      corrDiffMin = corrDiffMax = 0;\n\n   // Minimum: between two *possible* breaks.\n   // Shrinkability could be considered, but really does not play a role.\n   lastPar->parMin += wordExtremes.minWidth + word->hyphenWidth + corrDiffMin;\n   lastPar->parMinIntrinsic +=\n      wordExtremes.minWidthIntrinsic + word->hyphenWidth + corrDiffMin;\n   lastPar->parAdjustmentWidth +=\n      wordExtremes.adjustmentWidth + word->hyphenWidth + corrDiffMin;\n   lastPar->maxParMin = max (lastPar->maxParMin, lastPar->parMin);\n   lastPar->maxParMinIntrinsic =\n      max (lastPar->maxParMinIntrinsic, lastPar->parMinIntrinsic);\n   lastPar->maxParAdjustmentWidth =\n      max (lastPar->maxParAdjustmentWidth, lastPar->parAdjustmentWidth);\n\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1, \"parMin\",\n                           lastPar->parMin);\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                           \"parMinIntrinsic\", lastPar->parMinIntrinsic);\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1, \"maxParMin\",\n                           lastPar->maxParMin);\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                           \"maxParMinIntrinsic\", lastPar->maxParMinIntrinsic);\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                           \"parAdjustmentWidth\", lastPar->parAdjustmentWidth);\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                           \"maxParAdjustmentWidth\",\n                           lastPar->maxParAdjustmentWidth);\n\n   if (word->badnessAndPenalty.lineCanBeBroken (1) &&\n       (word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) {\n      lastPar->parMin = lastPar->parMinIntrinsic = lastPar->parAdjustmentWidth\n         = 0;\n\n      DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1, \"parMin\",\n                              lastPar->parMin);\n      DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                              \"parMinIntrinsic\", lastPar->parMinIntrinsic);\n      DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                              \"parAdjustmentWidth\",\n                              lastPar->parAdjustmentWidth);\n   }\n\n   // Maximum: between two *necessary* breaks.\n   lastPar->parMax += wordExtremes.maxWidth + word->hyphenWidth + corrDiffMax;\n   lastPar->parMaxIntrinsic +=\n      wordExtremes.maxWidthIntrinsic + word->hyphenWidth + corrDiffMax;\n   lastPar->maxParMax = max (lastPar->maxParMax, lastPar->parMax);\n   lastPar->maxParMaxIntrinsic =\n      max (lastPar->maxParMaxIntrinsic, lastPar->parMaxIntrinsic);\n\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1, \"parMax\",\n                           lastPar->parMax);\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                           \"parMaxIntrinsic\", lastPar->parMaxIntrinsic);\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1, \"maxParMax\",\n                           lastPar->maxParMax);\n   DBG_OBJ_ARRATTRSET_NUM (\"paragraphs\", paragraphs->size() - 1,\n                           \"maxParMaxIntrinsic\", lastPar->maxParMaxIntrinsic);\n   \n   lastPar->lastWord = wordIndex;\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Called when something changed for the last word (space, hyphens etc.).\n */\nvoid Textblock::correctLastWordExtremes ()\n{\n   if (paragraphs->size() > 0) {\n      Word *word = words->getLastRef ();\n      if (word->badnessAndPenalty.lineCanBeBroken (1) &&\n          (word->flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) == 0) {\n         Paragraph *lastPar = paragraphs->getLastRef();\n         lastPar->parMin = lastPar->parMinIntrinsic =\n            lastPar->parAdjustmentWidth = 0;\n         PRINTF (\"   => corrected; parMin = %d\\n\",\n                 paragraphs->getLastRef()->parMin);\n      }\n   }\n}\n\n\nint Textblock::hyphenateWord (int wordIndex, int *addIndex1)\n{\n   Word *hyphenatedWord = words->getRef(wordIndex);\n   char lang[3] = { hyphenatedWord->style->x_lang[0],\n                    hyphenatedWord->style->x_lang[1], 0 };\n   Hyphenator *hyphenator = Hyphenator::getHyphenator (lang);\n   PRINTF (\"[%p]    considering to hyphenate word %d, '%s', in language '%s'\\n\",\n           this, wordIndex, words->getRef(wordIndex)->content.text, lang);\n   int numBreaks;\n   int *breakPos =\n      hyphenator->hyphenateWord (layout->getPlatform (),\n                                 hyphenatedWord->content.text, &numBreaks);\n\n   if (numBreaks > 0) {\n      Word origWord = *hyphenatedWord;\n\n      core::Requisition wordSize[numBreaks + 1];\n      calcTextSizes (origWord.content.text, strlen (origWord.content.text),\n                     origWord.style, numBreaks, breakPos, wordSize);\n\n      PRINTF (\"[%p]       %d words ...\\n\", this, words->size ());\n      words->insert (wordIndex, numBreaks);\n\n      DBG_IF_RTFL {\n         for (int i = wordIndex + numBreaks; i < words->size (); i++)\n            DBG_SET_WORD (i);\n      }\n\n      for (int i = 0; i < numBreaks; i++)\n         initWord (wordIndex + i);\n      PRINTF (\"[%p]       ... => %d words\\n\", this, words->size ());\n\n      moveWordIndices (wordIndex, numBreaks, addIndex1);\n\n      // Adjust anchor indexes.\n      for (int i = 0; i < anchors->size (); i++) {\n         Anchor *anchor = anchors->getRef (i);\n         if (anchor->wordIndex > wordIndex)\n            anchor->wordIndex += numBreaks;\n      }\n\n      for (int i = 0; i < numBreaks + 1; i++) {\n         Word *w = words->getRef (wordIndex + i);\n         fillWord (wordIndex + i, wordSize[i].width, wordSize[i].ascent,\n                   wordSize[i].descent, false, origWord.style);\n\n         // TODO There should be a method fillText0.\n         w->content.type = core::Content::TEXT;\n\n         int start = (i == 0 ? 0 : breakPos[i - 1]);\n         int end = (i == numBreaks ?\n                    strlen (origWord.content.text) : breakPos[i]);\n         w->content.text =\n            layout->textZone->strndup (origWord.content.text + start,\n                                       end - start);\n\n         // Note: there are numBreaks + 1 word parts.\n         if (i == 0)\n            w->flags |= Word::WORD_START;\n         else\n            w->flags &= ~Word::WORD_START;\n\n         if (i == numBreaks)\n            w->flags |= Word::WORD_END;\n         else\n            w->flags &= ~Word::WORD_END;\n\n         if (i < numBreaks) {\n            // TODO There should be a method fillHyphen.\n            w->badnessAndPenalty.setPenalties (penalties[PENALTY_HYPHEN][0],\n                                               penalties[PENALTY_HYPHEN][1]);\n            // \"\\xe2\\x80\\x90\" is an unconditional hyphen.\n            w->hyphenWidth =\n               layout->textWidth (w->style->font, hyphenDrawChar,\n                                  strlen (hyphenDrawChar));\n            w->flags |= (Word::DRAW_AS_ONE_TEXT | Word::DIV_CHAR_AT_EOL |\n                         Word::UNBREAKABLE_FOR_MIN_WIDTH);\n         } else {\n            if (origWord.content.space)\n               fillSpace (wordIndex + i, origWord.spaceStyle);\n         }\n\n         DBG_SET_WORD (wordIndex + i);\n      }\n\n      // AccumulateWordData() will calculate the width, which depends\n      // on the borders (possibly limited by floats), which depends on\n      // the widgeds so far. For this reason, it is important to first\n      // make all words consistent before calling\n      // accumulateWordData(); therefore the second loop.\n\n      for (int i = 0; i < numBreaks + 1; i++)\n         accumulateWordData (wordIndex + i);\n\n      PRINTF (\"   finished\\n\");\n\n      //delete origword->content.text; TODO: Via textZone?\n      origWord.style->unref ();\n      origWord.spaceStyle->unref ();\n\n      free (breakPos);\n   } else\n      words->getRef(wordIndex)->flags &= ~Word::CAN_BE_HYPHENATED;\n\n   return numBreaks;\n}\n\nvoid Textblock::moveWordIndices (int wordIndex, int num, int *addIndex1)\n{\n   DBG_OBJ_ENTER (\"construct.word\", 0, \"moveWordIndices\", \"%d, %d\",\n                 wordIndex, num);\n\n   for (int i = 0; i < NUM_OOFM; i++)\n      if (searchOutOfFlowMgr(i))\n         searchOutOfFlowMgr(i)->moveExternalIndices (this, wordIndex, num);\n\n   for (int i = lines->size () - 1; i >= 0; i--) {\n      Line *line = lines->getRef (i);\n      if (line->lastOofRefPositionedBeforeThisLine < wordIndex) {\n         // Since lastOofRefPositionedBeforeThisLine are ascending,\n         // the search can be stopped here.\n         DBG_OBJ_MSGF (\"construct.word\", 1,\n                       \"lines[%d]->lastOofRef = %d < %d => stop\",\n                       i, line->lastOofRefPositionedBeforeThisLine, wordIndex);\n         break;\n      } else {\n         DBG_OBJ_MSGF (\"construct.word\", 1,\n                       \"adding %d to lines[%d]->lastOofRef...: %d -> %d\",\n                       num, i, line->lastOofRefPositionedBeforeThisLine,\n                       line->lastOofRefPositionedBeforeThisLine + num);\n         line->lastOofRefPositionedBeforeThisLine += num;\n      }\n   }\n\n   // Unlike the last line, the last paragraph is already constructed. (To\n   // make sure we cover all cases, we iterate over the last paragraphs.)\n   Paragraph *par;\n   for (int parNo = paragraphs->size () - 1;\n        parNo >= 0 &&\n           (par = paragraphs->getRef(parNo)) && par->lastWord > wordIndex;\n        parNo--) {\n      par->lastWord += num;\n      if (par->firstWord > wordIndex)\n         par->firstWord += num;\n   }\n\n   // Addiditional indices. When needed, the number can be extended.\n   if (addIndex1 && *addIndex1 >= wordIndex)\n      *addIndex1 += num;\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::accumulateWordForLine (int lineIndex, int wordIndex)\n{\n   DBG_OBJ_ENTER (\"construct.line\", 1, \"accumulateWordForLine\", \"%d, %d\",\n                  lineIndex, wordIndex);\n   DBG_MSG_WORD (\"construct.line\", 2, \"<i>word:</i> \", wordIndex, \"\");\n\n   Line *line = lines->getRef (lineIndex);\n   Word *word = words->getRef (wordIndex);\n\n   int len = word->style->font->ascent;\n   if (word->style->valign == core::style::VALIGN_SUPER)\n      len += len / 2;\n   line->contentAscent = max (line->contentAscent, len);\n\n   len = word->style->font->descent;\n   if (word->style->valign == core::style::VALIGN_SUB)\n      len += word->style->font->ascent / 3;\n   line->contentDescent = max (line->contentDescent, len);\n\n   int borderAscent, borderDescent, marginAscent, marginDescent;\n\n   DBG_OBJ_MSGF (\"construct.line\", 2, \"size.ascent = %d, size.descent = %d\",\n                 word->size.ascent, word->size.descent);\n\n   if (word->content.type == core::Content::WIDGET_IN_FLOW) {\n      // TODO Consider extraSpace?\n      marginAscent = word->size.ascent;\n      marginDescent = word->size.descent;\n      borderAscent =\n         marginAscent - word->content.widget->getStyle()->margin.top;\n      borderDescent =\n         marginDescent - word->content.widget->getStyle()->margin.bottom;\n\n      word->content.widget->parentRef = makeParentRefInFlow (lineIndex);\n      DBG_OBJ_SET_NUM_O (word->content.widget, \"parentRef\",\n                         word->content.widget->parentRef);\n   } else {\n      borderAscent = marginAscent = word->size.ascent;\n      borderDescent = marginDescent = word->size.descent;\n\n      if (word->content.type == core::Content::BREAK)\n         line->breakSpace = max (word->content.breakSpace, line->breakSpace);\n      else if (word->content.type == core::Content::WIDGET_OOF_REF) {\n         word->content.widgetReference->parentRef =\n            makeParentRefInFlow (lineIndex);\n            DBG_SET_WORD (wordIndex);\n      }\n   }\n\n   DBG_OBJ_MSGF (\"construct.line\", 2,\n                 \"borderAscent = %d, borderDescent = %d, marginAscent = %d, \"\n                 \"marginDescent = %d\",\n                 borderAscent, borderDescent, marginAscent, marginDescent);\n\n   line->borderAscent = max (line->borderAscent, borderAscent);\n   line->borderDescent = max (line->borderDescent, borderDescent);\n   line->marginAscent = max (line->marginAscent, marginAscent);\n   line->marginDescent = max (line->marginDescent, marginDescent);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::accumulateWordData (int wordIndex)\n{\n   DBG_OBJ_ENTER (\"construct.word.accum\", 1, \"accumulateWordData\", \"%d\",\n                 wordIndex);\n   DBG_MSG_WORD (\"construct.word.accum\", 1, \"<i>word:</i> \", wordIndex, \"\");\n\n   // Typically, the word in question is in the last line; in any case\n   // quite at the end of the text, so that linear search is actually\n   // the fastest option.\n   int lineIndex = lines->size ();\n   while (lineIndex > 0 && wordIndex <= lines->getRef(lineIndex - 1)->lastWord)\n      lineIndex--;\n\n   int firstWordOfLine;\n   if (lineIndex == 0)\n      firstWordOfLine = 0;\n   else\n      firstWordOfLine = lines->getRef(lineIndex - 1)->lastWord + 1;\n\n   Word *word = words->getRef (wordIndex);\n   DBG_OBJ_MSGF (\"construct.word.accum\", 2, \"lineIndex = %d\", lineIndex);\n\n   int lineBreakWidth = calcLineBreakWidth (lineIndex);\n\n   DBG_OBJ_MSGF (\"construct.word.accum\", 2,\n                 \"(%s existing line %d starts with word %d; \"\n                 \"lineBreakWidth = %d)\",\n                 lineIndex < lines->size () ? \"already\" : \"not yet\",\n                 lineIndex, firstWordOfLine, lineBreakWidth);\n\n   if (wordIndex == firstWordOfLine) {\n      // first word of the (not neccessarily yet existing) line\n      word->totalWidth = word->size.width + word->hyphenWidth;\n      word->maxAscent = word->size.ascent;\n      word->maxDescent = word->size.descent;\n      word->totalSpaceStretchability = 0;\n      word->totalSpaceShrinkability = 0;\n\n      DBG_OBJ_MSGF (\"construct.word.accum\", 1,\n                    \"first word of line: words[%d].totalWidth = %d + %d = %d; \"\n                    \"maxAscent = %d, maxDescent = %d\",\n                    wordIndex, word->size.width, word->hyphenWidth,\n                    word->totalWidth, word->maxAscent, word->maxDescent);\n   } else {\n      Word *prevWord = words->getRef (wordIndex - 1);\n\n      word->totalWidth = prevWord->totalWidth\n         + prevWord->origSpace - prevWord->hyphenWidth\n         + word->size.width + word->hyphenWidth;\n      word->maxAscent = max (prevWord->maxAscent, word->size.ascent);\n      word->maxDescent = max (prevWord->maxDescent, word->size.descent);\n      word->totalSpaceStretchability =\n         prevWord->totalSpaceStretchability + getSpaceStretchability(prevWord);\n      word->totalSpaceShrinkability =\n         prevWord->totalSpaceShrinkability + getSpaceShrinkability(prevWord);\n\n      DBG_OBJ_MSGF (\"construct.word.accum\", 1,\n                    \"not first word of line: words[%d].totalWidth = %d + %d - \"\n                    \"%d + %d + %d = %d; maxAscent = max (%d, %d) = %d, \"\n                    \"maxDescent = max (%d, %d) = %d\",\n                    wordIndex, prevWord->totalWidth, prevWord->origSpace,\n                    prevWord->hyphenWidth, word->size.width,\n                    word->hyphenWidth, word->totalWidth,\n                    prevWord->maxAscent, word->size.ascent, word->maxAscent,\n                    prevWord->maxDescent, word->size.descent, word->maxDescent);\n   }\n\n   int totalStretchability =\n      word->totalSpaceStretchability + getLineStretchability (wordIndex);\n   int totalShrinkability =\n      word->totalSpaceShrinkability + getLineShrinkability (wordIndex);\n\n   DBG_OBJ_MSGF (\"construct.word.accum\", 1,\n                 \"totalStretchability = %d + ... = %d\",\n                 word->totalSpaceStretchability, totalStretchability);\n   DBG_OBJ_MSGF (\"construct.word.accum\", 1,\n                 \"totalShrinkability = %d + ... = %d\",\n                 word->totalSpaceShrinkability, totalShrinkability);\n\n   word->badnessAndPenalty.calcBadness (word->totalWidth, lineBreakWidth,\n                                        totalStretchability,\n                                        totalShrinkability);\n\n   DBG_IF_RTFL {\n      StringBuffer sb;\n      word->badnessAndPenalty.intoStringBuffer (&sb);\n      DBG_OBJ_ARRATTRSET_SYM (\"words\", wordIndex, \"badnessAndPenalty\",\n                              sb.getChars ());\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nint Textblock::calcLineBreakWidth (int lineIndex)\n{\n   DBG_OBJ_ENTER (\"construct.word.width\", 1, \"calcLineBreakWidth\",\n                  \"%d <i>of %d</i>\", lineIndex, lines->size());\n\n   int lineBreakWidth = this->lineBreakWidth - leftInnerPadding;\n   if (limitTextWidth &&\n       layout->getUsesViewport () &&\n       // margin/border/padding will be subtracted later,  via OOFM.\n       lineBreakWidth - boxDiffWidth() > layout->getWidthViewport () - 10)\n      lineBreakWidth = layout->getWidthViewport () - 10;\n   if (lineIndex == 0)\n      lineBreakWidth -= line1OffsetEff;\n\n   int leftBorder, rightBorder;\n   if (mustBorderBeRegarded (lineIndex)) {\n      leftBorder = newLineLeftBorder;\n      rightBorder = newLineRightBorder;\n   } else\n      leftBorder = rightBorder = 0;\n\n   leftBorder = max (leftBorder, boxOffsetX());\n   rightBorder = max (rightBorder, boxRestWidth());\n\n   lineBreakWidth -= (leftBorder + rightBorder);\n\n   DBG_OBJ_LEAVE_VAL (\"%d - %d - (%d + %d) = %d\",\n                      this->lineBreakWidth, leftInnerPadding, leftBorder,\n                      rightBorder, lineBreakWidth);\n   return lineBreakWidth;\n}\n\nvoid Textblock::initLine1Offset (int wordIndex)\n{\n   Word *word = words->getRef (wordIndex);\n\n   /* Test whether line1Offset can be used. */\n   if (wordIndex == 0) {\n      if (ignoreLine1OffsetSometimes &&\n          line1Offset + word->size.width > lineBreakWidth) {\n         line1OffsetEff = 0;\n      } else {\n         int indent = 0;\n\n         if (word->content.type == core::Content::WIDGET_IN_FLOW &&\n             word->content.widget->isBlockLevel()) {\n            /* don't use text-indent when nesting blocks */\n         } else {\n            if (core::style::isPerLength(getStyle()->textIndent)) {\n               indent = core::style::multiplyWithPerLengthRounded\n                           (lineBreakWidth, getStyle()->textIndent);\n            } else {\n               indent = core::style::absLengthVal (getStyle()->textIndent);\n            }\n         }\n         line1OffsetEff = line1Offset + indent;\n      }\n   }\n}\n\n/**\n * Align the line.\n *\n * \\todo Use block's style instead once paragraphs become proper blocks.\n */\nvoid Textblock::alignLine (int lineIndex)\n{\n   DBG_OBJ_ENTER (\"construct.line\", 0, \"alignLine\", \"%d\", lineIndex);\n\n   Line *line = lines->getRef (lineIndex);\n\n   for (int i = line->firstWord; i <= line->lastWord; i++)\n      words->getRef(i)->effSpace = words->getRef(i)->origSpace;\n\n   // We are not interested in the alignment of floats etc.\n   int firstWordNotOofRef = line->firstWord;\n   while (firstWordNotOofRef <= line->lastWord &&\n          words->getRef(firstWordNotOofRef)->content.type\n          == core::Content::WIDGET_OOF_REF)\n      firstWordNotOofRef++;\n\n   if (firstWordNotOofRef <= line->lastWord) {\n      Word *firstWord = words->getRef (firstWordNotOofRef);\n\n      if (firstWord->content.type != core::Content::BREAK) {\n         Word *lastWord = words->getRef (line->lastWord);\n         int lineBreakWidth =\n            this->lineBreakWidth - (line->leftOffset + line->rightOffset);\n\n         switch (firstWord->style->textAlign) {\n         case core::style::TEXT_ALIGN_LEFT:\n            DBG_OBJ_MSG (\"construct.line\", 1,\n                         \"first word has 'text-align: left'\");\n            line->alignment = Line::LEFT;\n            break;\n         case core::style::TEXT_ALIGN_STRING:   /* handled elsewhere (in the\n                                                 * future)? */\n            DBG_OBJ_MSG (\"construct.line\", 1,\n                         \"first word has 'text-align: string'\");\n            line->alignment = Line::LEFT;\n            break;\n         case core::style::TEXT_ALIGN_JUSTIFY:  /* see some lines above */\n            DBG_OBJ_MSG (\"construct.line\", 1,\n                         \"first word has 'text-align: justify'\");\n            line->alignment = Line::LEFT;\n            // Do not justify the last line of a paragraph (which ends on a\n            // BREAK or with the last word of the page).\n            if(!(lastWord->content.type == core::Content::BREAK ||\n                 line->lastWord == words->size () - 1) ||\n               // In some cases, however, an unjustified line would be too wide:\n               // when the line would be shrunken otherwise. (This solution is\n               // far from perfect, but a better solution would make changes in\n               // the line breaking algorithm necessary.)\n               lineBreakWidth < lastWord->totalWidth)\n               justifyLine (line, lineBreakWidth - lastWord->totalWidth);\n            break;\n         case core::style::TEXT_ALIGN_RIGHT:\n            DBG_OBJ_MSG (\"construct.line\", 1,\n                         \"first word has 'text-align: right'\");\n            line->alignment = Line::RIGHT;\n            break;\n         case core::style::TEXT_ALIGN_CENTER:\n            DBG_OBJ_MSG (\"construct.line\", 1,\n                         \"first word has 'text-align: center'\");\n            line->alignment = Line::CENTER;\n            break;\n         default:\n            // compiler happiness\n            line->alignment = Line::LEFT;\n         }\n\n      } else\n         // empty line (only line break);\n         line->alignment = Line::LEFT;\n   } else\n      // empty line (or only OOF references).\n      line->alignment = Line::LEFT;\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::calcTextOffset (int lineIndex, int totalWidth)\n{\n   DBG_OBJ_ENTER (\"construct.line\", 0, \"calcTextOffset\", \"%d, %d\",\n                  lineIndex, totalWidth);\n\n   Line *line = lines->getRef (lineIndex);\n   int lineWidth = line->firstWord <= line->lastWord ?\n      words->getRef(line->lastWord)->totalWidth : 0;\n\n   switch (line->alignment) {\n   case Line::LEFT:\n      line->textOffset = line->leftOffset;\n      DBG_OBJ_MSGF (\"construct.line\", 1, \"left: textOffset = %d\",\n                    line->textOffset);\n      break;\n\n   case Line::RIGHT:\n      line->textOffset = totalWidth - line->rightOffset - lineWidth;\n      DBG_OBJ_MSGF (\"construct.line\", 1,\n                    \"right: textOffset = %d - %d - %d = %d\",\n                    totalWidth, line->rightOffset, lineWidth, line->textOffset);\n      break;\n\n   case Line::CENTER:\n      line->textOffset =\n         (line->leftOffset + totalWidth - line->rightOffset - lineWidth) / 2;\n      DBG_OBJ_MSGF (\"construct.line\", 1,\n                    \"center: textOffset = (%d + %d - %d - %d) /2 = %d\",\n                    line->leftOffset, totalWidth, line->rightOffset, lineWidth,\n                    line->textOffset);\n      break;\n\n   default:\n      assertNotReached ();\n      break;\n   }\n\n   // For large lines (images etc), which do not fit into the viewport:\n   if (line->textOffset < line->leftOffset)\n      line->textOffset = line->leftOffset;\n\n   DBG_OBJ_ARRATTRSET_NUM (\"lines\", lineIndex, \"textOffset\", line->textOffset);\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Rewrap the page from the line from which this is necessary.\n * There are basically two times we'll want to do this:\n * either when the viewport is resized, or when the size changes on one\n * of the child widgets.\n */\nvoid Textblock::rewrap ()\n{\n   DBG_OBJ_ENTER0 (\"construct.line\", 0, \"rewrap\");\n\n   if (wrapRefLines == -1)\n      DBG_OBJ_MSG (\"construct.line\", 0, \"does not have to be rewrapped\");\n   else {\n      // All lines up from wrapRef will be rebuild from the word list,\n      // the line list up from this position is rebuild.\n      lines->setSize (wrapRefLines);\n      DBG_OBJ_SET_NUM (\"lines.size\", lines->size ());\n      nonTemporaryLines = min (nonTemporaryLines, wrapRefLines);\n\n      initNewLine ();\n\n      int firstWord;\n      if (lines->size () > 0) {\n         Line *lastLine = lines->getLastRef();\n         firstWord = lastLine->lastWord + 1;\n      } else\n         firstWord = 0;\n\n      DBG_OBJ_MSGF (\"construct.line\", 0, \"starting with word %d\", firstWord);\n\n      lastWordDrawn = min (lastWordDrawn, firstWord - 1);\n      DBG_OBJ_SET_NUM (\"lastWordDrawn\", lastWordDrawn);\n\n      for (int i = firstWord; i < words->size (); i++) {\n         Word *word = words->getRef (i);\n\n         switch (word->content.type) {\n         case core::Content::WIDGET_IN_FLOW:\n            calcSizeOfWidgetInFlow (i, word->content.widget,  &word->size);\n            DBG_SET_WORD_SIZE (i);\n            break;\n            \n         case core::Content::WIDGET_OOF_REF:\n            {\n               int oofmIndex =\n                  getOOFMIndex (word->content.widgetReference->widget);\n               oof::OutOfFlowMgr *oofm = searchOutOfFlowMgr (oofmIndex);\n               oofm->calcWidgetRefSize (word->content.widgetReference->widget,\n                                        &(word->size));\n               DBG_SET_WORD_SIZE (i);\n            }\n            break;\n            \n         default:\n            break;\n         }\n\n         wordWrap (i, false);\n\n         // Somewhat historical, but still important, note:\n         //\n         // For the case that something else is done with this word, it\n         // is important that wordWrap() may insert some new words; since\n         // NotSoSimpleVector is used for the words list, the internal\n         // structure may have changed, so getRef() must be called again.\n         //\n         // So this is necessary: word = words->getRef (i);\n      }\n\n      // Next time, the page will not have to be rewrapped.\n      wrapRefLines = -1;\n      DBG_OBJ_SET_NUM (\"wrapRefLines\", wrapRefLines);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * Counter part to rewrap(), but for extremes, not size calculation.\n */\nvoid Textblock::fillParagraphs ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"fillParagraphs\");\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"wrapRefParagraphs = %d\", wrapRefParagraphs);\n\n   if (wrapRefParagraphs != -1) {\n      // Notice that wrapRefParagraphs refers to the lines, not to the\n      // paragraphs.\n      int firstWordOfLine;\n      if (lines->size () > 0 && wrapRefParagraphs > 0) {\n         // Sometimes, wrapRefParagraphs is larger than lines->size(), due to\n         // floats? (Has to be clarified.)\n         int lineNo = min (wrapRefParagraphs, lines->size ()) - 1;\n         firstWordOfLine = lines->getRef(lineNo)->lastWord + 1;\n      } else\n         firstWordOfLine = 0;\n\n      int parNo;\n      if (paragraphs->size() > 0 &&\n          firstWordOfLine > paragraphs->getLastRef()->firstWord)\n         // A special case: the paragraphs list has been partly built, but\n         // not yet the paragraph containing the word in question. In\n         // this case, only the rest of the paragraphs list must be\n         // constructed. (Without this check, findParagraphOfWord would\n         // return -1 in this case, so that all paragraphs would be\n         // rebuilt.)\n         parNo = paragraphs->size ();\n      else\n         // If there are no paragraphs yet, findParagraphOfWord will return\n         // -1: use 0 then instead.\n         parNo = max (0, findParagraphOfWord (firstWordOfLine));\n\n      paragraphs->setSize (parNo);\n      DBG_OBJ_SET_NUM (\"paragraphs.size\", paragraphs->size ());\n\n      int firstWord;\n      if (paragraphs->size () > 0)\n         firstWord = paragraphs->getLastRef()->lastWord + 1;\n      else\n         firstWord = 0;\n\n      DBG_OBJ_MSGF (\"resize\", 1, \"firstWord = %d, words->size() = %d [before]\",\n                    firstWord, words->size ());\n\n      for (int i = firstWord; i < words->size (); i++)\n         handleWordExtremes (i);\n\n      DBG_OBJ_MSGF (\"resize\", 1, \"words->size() = %d [after]\", words->size ());\n\n      wrapRefParagraphs = -1;\n      DBG_OBJ_SET_NUM (\"wrapRefParagraphs\", wrapRefParagraphs);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::initNewLine ()\n{\n   DBG_OBJ_ENTER0 (\"construct.line\", 0, \"initNewLine\");\n\n   calcBorders (lines->size() > 0 ?\n                lines->getLastRef()->lastOofRefPositionedBeforeThisLine : -1,\n                1);\n\n   newLineAscent = newLineDescent = 0;\n\n   DBG_OBJ_SET_NUM (\"newLineAscent\", newLineAscent);\n   DBG_OBJ_SET_NUM (\"newLineDescent\", newLineDescent);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::calcBorders (int lastOofRef, int height)\n{\n   DBG_OBJ_ENTER (\"construct.line\", 0, \"calcBorders\", \"%d, %d\",\n                 lastOofRef, height);\n\n   newLineHasFloatLeft = newLineHasFloatRight = false;\n   newLineLeftBorder = newLineRightBorder = 0;\n   newLineLeftFloatHeight = newLineRightFloatHeight = 0;\n\n   bool oofmDefined = false;\n   for (int i = 0; i < NUM_OOFM && !oofmDefined; i++)\n      if (findSizeRequestReference (i))\n         oofmDefined = true;\n\n   if (oofmDefined) {\n      int firstWordOfLine =\n         lines->size() > 0 ? lines->getLastRef()->lastWord + 1 : 0;\n      int effOofRef = max (lastOofRef, firstWordOfLine - 1);\n      int yRel = yOffsetOfLineToBeCreated (), yRef;\n           \n      for (int i = 0; i < NUM_OOFM; i++) {\n         oof::OutOfFlowMgr *oofm;\n         if ((oofm = searchOutOfFlowMgr (i)) &&\n             findSizeRequestReference (i, NULL, &yRef)) {\n            // Consider the example:\n            //\n            // <div>\n            //   Some text A ...\n            //   <p> Some text B ... <img style=\"float:right\" ...> </p>\n            //   Some more text C ...\n            // </div>\n            //\n            // If the image is large enough, it should float around the last\n            // paragraph, \"Some more text C ...\":\n            //\n            //    Some more text A ...\n            //\n            //    Some more  ,---------.\n            //    text B ... |         |\n            //               |  <img>  |\n            //    Some more  |         | <---- Consider this line!\n            //    text C ... '---------'\n            //\n            // Since this float is generated in the <p> element, not in the-\n            // <div> element, and since they are represented by different\n            // instances of dw::Textblock, lastOofRefPositionedBeforeThisLine,\n            // and so lastOofRef, is -1 for the line marked with an arrow;\n            // this would result in ignoring the float, because -1 is\n            // equivalent to the very beginning of the <div> element (\"Some\n            // more text A ...\"), which is not affected by the float.\n            //\n            // On the other hand, the only relevant values of\n            // Line::lastOofRefPositionedBeforeThisLine are those greater\n            // than the first word of the new line, so a solution is to use\n            // the maximum of both.\n\n            int y = yRef + yRel;\n            bool thisHasLeft, thisHasRight;\n            \n            thisHasLeft = oofm->hasFloatLeft (y, height, this, effOofRef);\n            newLineHasFloatLeft = newLineHasFloatLeft || thisHasLeft;\n            thisHasRight = oofm->hasFloatRight (y, height, this, effOofRef);\n            newLineHasFloatRight = newLineHasFloatRight || thisHasRight;\n                       \n            // TODO \"max\" is not really correct for the heights. (Does\n            // not matter, since only one, the float manager, returns\n            // meaningful values.)\n            if (thisHasLeft) {\n               newLineLeftBorder =\n                  max (newLineLeftBorder,\n                       oofm->getLeftBorder (y, height, this, effOofRef)\n                       - getGeneratorX (i));\n               newLineLeftFloatHeight =\n                  max (newLineLeftFloatHeight,\n                       oofm->getLeftFloatHeight (y, height, this, effOofRef));\n            }\n\n            if (thisHasRight) {\n               newLineRightBorder =\n                  max (newLineRightBorder,\n                       oofm->getRightBorder (y, height, this, effOofRef)\n                       - getGeneratorRest (i));\n               newLineRightFloatHeight = \n                  max (newLineRightFloatHeight,\n                       oofm->getRightFloatHeight (y, height, this, effOofRef));\n            }\n            \n            DBG_OBJ_MSGF (\"construct.line\", 1,\n                          \"OOFM #%d: %d * %d (%s) / %d * %d (%s), at %d (%d), \"\n                          \"until %d = max (%d, %d - 1)\",\n                          i, newLineLeftBorder, newLineLeftFloatHeight,\n                          newLineHasFloatLeft ? \"true\" : \"false\",\n                          newLineRightBorder, newLineRightFloatHeight,\n                          newLineHasFloatRight ? \"true\" : \"false\",\n                          y, height, effOofRef, lastOofRef, firstWordOfLine);\n         }\n      }\n   }\n\n   DBG_OBJ_SET_BOOL (\"newLineHasFloatLeft\", newLineHasFloatLeft);\n   DBG_OBJ_SET_BOOL (\"newLineHasFloatRight\", newLineHasFloatRight);\n   DBG_OBJ_SET_NUM (\"newLineLeftBorder\", newLineLeftBorder);\n   DBG_OBJ_SET_NUM (\"newLineRightBorder\", newLineRightBorder);\n   DBG_OBJ_SET_NUM (\"newLineLeftFloatHeight\", newLineLeftFloatHeight);\n   DBG_OBJ_SET_NUM (\"newLineRightFloatHeight\", newLineRightFloatHeight);\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Textblock::showMissingLines ()\n{\n   DBG_OBJ_ENTER0 (\"construct.line\", 0, \"showMissingLines\");\n\n   // \"Temporary word\": when the last word is an OOF reference, it is\n   // not processed, and not part of any line. For this reason, we\n   // introduce a \"temporary word\", which is in flow, after this last\n   // OOF reference, and later removed again.\n   bool tempWord = words->size () > 0 &&\n      words->getLastRef()->content.type == core::Content::WIDGET_OOF_REF;\n   int firstWordToWrap =\n      lines->size () > 0 ? lines->getLastRef()->lastWord + 1 : 0;\n\n   DBG_OBJ_MSGF (\"construct.line\", 1,\n                 \"words->size() = %d, firstWordToWrap = %d, tempWord = %s\",\n                 words->size (), firstWordToWrap, tempWord ? \"true\" : \"false\");\n\n   if (tempWord) {\n      core::Requisition size = { 0, 0, 0 };\n      addText0 (\"\", 0, Word::WORD_START | Word::WORD_END, getStyle (), &size);\n   }\n\n   for (int i = firstWordToWrap; i < words->size (); i++)\n      wordWrap (i, true);\n\n   // Remove temporary word again. The only reference should be the line.\n   if (tempWord) {\n      cleanupWord (words->size () - 1);\n      words->setSize (words->size () - 1);\n      if (lines->getLastRef()->lastWord > words->size () - 1)\n         lines->getLastRef()->lastWord = words->size () - 1;\n   }\n\n   // The following old code should not be necessary anymore, after\n   // the introduction of the \"virtual word\". Instead, test the\n   // condition.\n   assert (lines->size () == 0 ||\n           lines->getLastRef()->lastWord == words->size () - 1);\n   /*\n   // In some cases, there are some words of type WIDGET_OOF_REF left, which\n   // are not added to line, since addLine() is only called within\n   // wrapWordInFlow(), but not within wrapWordOofRef(). The missing line\n   // is created here, so it is ensured that the last line ends with the last\n   // word.\n\n   int firstWordNotInLine =\n      lines->size () > 0 ? lines->getLastRef()->lastWord + 1: 0;\n   DBG_OBJ_MSGF (\"construct.line\", 1, \"firstWordNotInLine = %d (of %d)\",\n                 firstWordNotInLine, words->size ());\n   if (firstWordNotInLine < words->size ())\n      addLine (firstWordNotInLine, words->size () - 1, -1, true);\n   */\n\n   DBG_OBJ_LEAVE ();\n}\n\n\nvoid Textblock::removeTemporaryLines ()\n{\n   DBG_OBJ_ENTER0 (\"construct.line\", 0, \"removeTemporaryLines\");\n\n   if (nonTemporaryLines < lines->size ()) {\n      lines->setSize (nonTemporaryLines);\n      DBG_OBJ_SET_NUM (\"lines.size\", lines->size ());\n\n      // For words which will be added, the values calculated before in\n      // accumulateWordData() are wrong, so it is called again. (Actually, the\n      // words from the first temporary line are correct, but for simplicity,\n      // we re-calculate all.)\n      int firstWord =\n         lines->size () > 0 ? lines->getLastRef()->lastWord + 1 : 0;\n      for (int i = firstWord; i < words->size (); i++)\n         accumulateWordData (i);\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nint Textblock::getSpaceShrinkability(struct Word *word)\n{\n   if (word->spaceStyle->textAlign == core::style::TEXT_ALIGN_JUSTIFY)\n      return word->origSpace / 3;\n   else\n      return 0;\n}\n\nint Textblock::getSpaceStretchability(struct Word *word)\n{\n   if (word->spaceStyle->textAlign == core::style::TEXT_ALIGN_JUSTIFY)\n      return word->origSpace / 2;\n   else\n      return 0;\n\n   // Alternative: return word->origSpace / 2;\n}\n\nint Textblock::getLineShrinkability(int lastWordIndex)\n{\n   return 0;\n}\n\nint Textblock::getLineStretchability(int lastWordIndex)\n{\n   DBG_OBJ_ENTER (\"construct.word.accum\", 0, \"getLineStretchability\", \"%d\",\n                  lastWordIndex);\n   DBG_MSG_WORD (\"construct.word.accum\", 1, \"<i>last word:</i> \",\n                 lastWordIndex, \"\");\n\n   Word *lastWord = words->getRef (lastWordIndex);\n   int str;\n\n   if (lastWord->spaceStyle->textAlign == core::style::TEXT_ALIGN_JUSTIFY) {\n      str = 0;\n      DBG_OBJ_MSG (\"construct.word.accum\", 1, \"justified => 0\");\n   } else {\n      str = stretchabilityFactor * (lastWord->maxAscent\n                                    + lastWord->maxDescent) / 100;\n      DBG_OBJ_MSGF (\"construct.word.accum\", 1,\n                    \"not justified => %d * (%d + %d) / 100 = %d\",\n                    stretchabilityFactor, lastWord->maxAscent,\n                    lastWord->maxDescent, str);\n   }\n\n   DBG_OBJ_LEAVE ();\n   return str;\n\n   // Alternative: return 0;\n}\n\n} // namespace dw\n"
  },
  {
    "path": "dw/tools.cc",
    "content": "#include \"core.hh\"\n\nnamespace dw {\nnamespace core {\n\nusing namespace lout::misc;\n   \nSizeParams::SizeParams ()\n{\n   DBG_OBJ_CREATE (\"dw::core::SizeParams\");\n   init ();\n   debugPrint ();\n}\n\nSizeParams::SizeParams (int numPos, Widget **references, int *x, int *y)\n{\n   DBG_OBJ_CREATE (\"dw::core::SizeParams\");\n   init ();\n   fill (numPos, references, x, y);\n   debugPrint ();\n}\n\nSizeParams::SizeParams (const SizeParams &other)\n{\n   DBG_OBJ_CREATE (\"dw::core::SizeParams\");\n   init ();\n   fill (other.numPos, other.references, other.x, other.y);\n   debugPrint ();\n}\n   \nSizeParams::~SizeParams ()\n{\n   cleanup ();\n   DBG_OBJ_DELETE ();\n}\n\nSizeParams &SizeParams::operator=(const SizeParams &other)\n{\n   cleanup ();\n   init ();\n   fill (other.numPos, other.references, other.x, other.y);\n   debugPrint ();\n   return *this;\n}\n\nvoid SizeParams::init ()\n{\n   numPos = 0;\n   references = NULL;\n   x = y = NULL;\n}\n\nvoid SizeParams::cleanup ()\n{\n   if (references)\n      delete[] references;\n   if (x)\n      delete[] x;\n   if (y)\n      delete[] y;\n\n   init ();\n}\n\nvoid SizeParams::fill (int numPos, Widget **references, int *x, int *y)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"fill\", \"%d, ...\", numPos);\n\n   cleanup ();\n   \n   this->numPos = numPos;\n\n   this->references = new Widget*[numPos];\n   this->x = new int[numPos];\n   this->y = new int[numPos];\n   \n   for (int i = 0; i < numPos; i++) {\n      this->references[i] = references[i];\n      this->x[i] = x[i];\n      this->y[i] = y[i];\n   }\n\n   debugPrint ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid SizeParams::forChild (Widget *parent, Widget *child, int xRel, int yRel,\n                           SizeParams *childParams)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"forChild\", \"%p, %p, %d, %d, %p\",\n                  parent, child, xRel, yRel, childParams);\n\n   childParams->cleanup ();\n   \n   int numChildReferences = child->numSizeRequestReferences ();\n\n   childParams->numPos = 0;\n   childParams->references = new Widget*[numChildReferences];\n   childParams->x = new int[numChildReferences];\n   childParams->y = new int[numChildReferences];\n\n   for (int i = 0; i < numChildReferences; i++) {\n      Widget *childReference = child->sizeRequestReference (i);\n      \n      if (childReference == parent) {\n         childParams->references[childParams->numPos] = childReference;\n         childParams->x[childParams->numPos] = xRel;\n         childParams->y[childParams->numPos] = yRel;\n         childParams->numPos++;\n      } else {\n         bool found = false;\n         for (int j = 0; !found && j < numPos; j++) {\n            if (childReference == references[j]) {\n               found = true;\n               childParams->references[childParams->numPos] = childReference;\n               childParams->x[childParams->numPos] = x[j] + xRel;\n               childParams->y[childParams->numPos] = y[j] + yRel;\n               childParams->numPos++;\n            } \n         }\n      }\n   }\n\n   childParams->debugPrint ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nbool SizeParams::findReference (Widget *reference, int *x, int *y)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"findReference\", \"%p\", reference);\n\n   bool found = false;\n   for (int i = 0; i < numPos && !found; i++) {\n      if (reference == references[i]) {\n         if (x)\n            *x = this->x[i];\n         if (y)\n            *y = this->y[i];\n         found = true;\n      }\n   }\n\n   if (found) {\n      if (x && y)\n         DBG_OBJ_LEAVE_VAL (\"true, x = %d, y = %d\", *x, *y);\n      else if (x)\n         DBG_OBJ_LEAVE_VAL (\"true, x = %d\", *x);\n      else if (y)\n         DBG_OBJ_LEAVE_VAL (\"true, y = %d\", *y);\n      else\n         DBG_OBJ_LEAVE_VAL (\"%s\", \"true\");\n   } else\n      DBG_OBJ_LEAVE_VAL (\"%s\", \"false\");\n\n   return found;\n}\n\n/**\n * Compares two instances, but considers a change in the order of the reference\n * widgets as equivalent.\n */\nbool SizeParams::isEquivalent (SizeParams *other)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"isEquivalent\", \"%p\", other);\n   bool result;\n   \n   if (numPos != other->numPos)\n      result = false;\n   else {\n      result = true;\n      \n      for (int i = 0; result && i < numPos; i++) {\n         bool otherFound = false;\n         for (int j = 0; !otherFound && j < numPos; j++) {\n            DBG_OBJ_MSGF (\"resize\", 1,\n                          \"#%d = (%p, %d, %d) vs. #%d = (%p, %d, %d)\",\n                          i, references[i], x[i], y[i], j, other->references[j],\n                          other->x[j], other->y[j]);\n            \n            if (references[i] == other->references[j]) {\n               otherFound = true;\n               if (!(x[i] == other->x[j] && y[i] == other->y[j]))\n                  result = false;\n            }\n         }\n         \n         if (!otherFound)\n            result = false;\n      }\n   }\n   \n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr (result));\n   return result;\n}\n  \n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/tools.hh",
    "content": "#ifndef __DW_TOOLS_HH__\n#define __DW_TOOLS_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\n#include \"core.hh\"\n#include \"../lout/debug.hh\"\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief Hold arguments passed to dw::core::Widget::sizeRequest and\n * dw::core::Widget::getExtremes, as described in \\ref dw-size-request-pos.\n */\nclass SizeParams\n{\nprivate:\n   int numPos;\n   Widget **references;\n   int *x, *y;\n\n   void init ();\n   void cleanup ();\n\n   inline void debugPrint () {\n      DBG_IF_RTFL {\n         DBG_OBJ_SET_NUM (\"numPos\", numPos);\n         for (int i = 0; i < numPos; i++) {\n            DBG_OBJ_ARRSET_PTR (\"references\", i, references[i]);\n            DBG_OBJ_ARRSET_NUM (\"x\", i, x[i]);\n            DBG_OBJ_ARRSET_NUM (\"y\", i, y[i]);\n         }\n      }\n   }\n   \npublic:\n   SizeParams ();\n   SizeParams (int numPos, Widget **references, int *x, int *y);\n   SizeParams (const SizeParams &other);\n   ~SizeParams ();\n\n   SizeParams &operator=(const SizeParams &other);\n   \n   void fill (int numPos, Widget **references, int *x, int *y);\n   void forChild (Widget *parent, Widget *child, int xRel, int yRel,\n                  SizeParams *childParams);\n   bool findReference (Widget *reference, int *x, int *y);\n\n   bool isEquivalent (SizeParams *other);\n\n   inline int getNumPos () { return numPos; }\n   inline Widget **getReferences () { return references; }\n   inline int *getX () { return x; }\n   inline int *getY () { return y; }\n   inline Widget *getReference (int i) { return references[i]; }\n   inline int getX (int i) { return x[i]; }\n   inline int getY (int i) { return y[i]; }\n};\n \n} // namespace core\n} // namespace dw\n\n#endif // __DW_TOOLS_HH__\n"
  },
  {
    "path": "dw/types.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"core.hh\"\n#include \"../lout/msg.h\"\n\nusing namespace lout;\n\nnamespace dw {\nnamespace core {\n\nRectangle::Rectangle (int x, int y, int width, int height)\n{\n   this->x = x;\n   this->y = y;\n   this->width = width;\n   this->height = height;\n}\n\n/*\n * Draw rectangle in view relative to point (x,y).\n */\nvoid Rectangle::draw (core::View *view, core::style::Style *style, int x,int y)\n{\n   const bool filled = false;\n\n   view->drawRectangle(style->color, core::style::Color::SHADING_NORMAL,filled,\n                       x + this->x, y + this->y, this->width, this->height);\n}\n\n/**\n * Return whether this rectangle and otherRect intersect. If yes,\n * return the intersection rectangle in dest.\n */\nbool Rectangle::intersectsWith (Rectangle *otherRect, Rectangle *dest)\n{\n   bool doIntersect =\n      this->x < otherRect->x + otherRect->width &&\n      this->y < otherRect->y + otherRect->height &&\n      otherRect->x < this->x + this->width &&\n      otherRect->y < this->y + this->height;\n\n   if (doIntersect) {\n      dest->x = misc::max(this->x, otherRect->x);\n      dest->y = misc::max(this->y, otherRect->y);\n      dest->width  = misc::min(this->x + this->width,\n                               otherRect->x + otherRect->width) - dest->x;\n      dest->height = misc::min(this->y + this->height,\n                               otherRect->y + otherRect->height) - dest->y;\n   } else {\n      dest->x = dest->y = dest->width = dest->height = 0;\n   }\n\n   return doIntersect;\n}\n\n/*\n * Return whether this is a subset of otherRect.\n */\nbool Rectangle::isSubsetOf (Rectangle *otherRect)\n{\n   return\n      x >= otherRect->x &&\n      y >= otherRect->y &&\n      x + width <= otherRect->x + otherRect->width &&\n      y + height <= otherRect->y + otherRect->height;\n}\n\nbool Rectangle::isPointWithin (int x, int y)\n{\n   return\n      x >= this->x && y >= this->y &&\n      x < this->x + width && y < this->y + height;\n}\n\n// ----------------------------------------------------------------------\n\nCircle::Circle (int x, int y, int radius)\n{\n   this->x = x;\n   this->y = y;\n   this->radius = radius;\n}\n\n/*\n * Draw circle in view relative to point (x,y).\n */\nvoid Circle::draw (core::View *view, core::style::Style *style, int x, int y)\n{\n   const bool filled = false;\n\n   view->drawArc(style->color, core::style::Color::SHADING_NORMAL, filled,\n                 x + this->x, y + this->y, 2 * this->radius, 2 * this->radius,\n                 0, 360);\n}\n\nbool Circle::isPointWithin (int x, int y)\n{\n   return\n      (x - this->x) * (x - this->x) + (y - this->y) * (y - this->y)\n      <= radius * radius;\n}\n\n// ----------------------------------------------------------------------\n\nPolygon::Polygon ()\n{\n   points = new misc::SimpleVector<Point> (8);\n   minx = miny = 0xffffff;\n   maxx = maxy = -0xffffff;\n}\n\nPolygon::~Polygon ()\n{\n   delete points;\n}\n\n/*\n * Draw polygon in view relative to point (x,y).\n */\nvoid Polygon::draw (core::View *view, core::style::Style *style, int x, int y)\n{\n   if (points->size()) {\n      int i;\n      const bool filled = false, convex = false;\n      Point *pointArray = (Point *)malloc(points->size()*sizeof(struct Point));\n\n      for (i = 0; i < points->size(); i++) {\n         pointArray[i].x = x + points->getRef(i)->x;\n         pointArray[i].y = y + points->getRef(i)->y;\n      }\n      view->drawPolygon(style->color, core::style::Color::SHADING_NORMAL,\n                        filled, convex, pointArray, i);\n      free(pointArray);\n   }\n}\n\nvoid Polygon::addPoint (int x, int y)\n{\n   points->increase ();\n   points->getRef(points->size () - 1)->x = x;\n   points->getRef(points->size () - 1)->y = y;\n\n   minx = misc::min(minx, x);\n   miny = misc::min(miny, y);\n   maxx = misc::max(maxx, x);\n   maxy = misc::max(maxy, y);\n}\n\n/**\n * \\brief Return, whether the line, limited by (ax1, ay1) and (ax2, ay2),\n *    crosses the unlimited line, determined by two points (bx1, by1) and\n *    (bx2, by2).\n */\nbool Polygon::linesCross0(int ax1, int ay1, int ax2, int ay2,\n                          int bx1, int by1, int bx2, int by2)\n{\n   /** TODO Some more description */\n   // If the scalar product is 0, it means that one point is on the second\n   // line, so we check for <= 0, not < 0.\n   int z1 = zOfVectorProduct (ax1 - bx1, ay1 - by1, bx2 - bx1, by2 - by1);\n   int z2 = zOfVectorProduct (ax2 - bx1, ay2 - by1, bx2 - bx1, by2 - by1);\n\n   return (z1 <= 0 && z2 >= 0) || (z1 >= 0 && z2 <= 0);\n}\n\n/**\n * \\brief Return, whether the line, limited by (ax1, ay1) and (ax2, ay2),\n *    crosses the line, limited by (bx1, by1) and (bx2, by2).\n */\nbool Polygon::linesCross(int ax1, int ay1, int ax2, int ay2,\n                         int bx1, int by1, int bx2, int by2)\n{\n   bool cross =\n      linesCross0 (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2) &&\n      linesCross0 (bx1, by1, bx2, by2, ax1, ay1, ax2, ay2);\n   _MSG(\"(%d, %d) - (%d, %d) and (%d, %d) - (%d, %d) cross? %s.\\n\",\n        ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, cross ? \"Yes\" : \"No\");\n   return cross;\n}\n\nbool Polygon::isPointWithin (int x, int y)\n{\n   if (points->size () < 3 ||\n       (x < minx || x > maxx || y < miny || y >= maxy))\n      return false;\n   else {\n      int numCrosses = 0;\n      for (int i = 0; i < points->size () - 1; i++) {\n         if (linesCross (minx - 1, miny - 1, x, y,\n                         points->getRef(i)->x, points->getRef(i)->y,\n                         points->getRef(i + 1)->x, points->getRef(i + 1)->y))\n            numCrosses++;\n      }\n      if (linesCross (minx - 1, miny - 1, x, y,\n                      points->getRef(points->size () - 1)->x,\n                      points->getRef(points->size () - 1)->y,\n                      points->getRef(0)->x, points->getRef(0)->y))\n         numCrosses++;\n\n      return numCrosses % 2 == 1;\n   }\n}\n\nRegion::Region()\n{\n   rectangleList = new container::typed::List <Rectangle> (true);\n}\n\nRegion::~Region()\n{\n   delete rectangleList;\n}\n\n/**\n * \\brief Add a rectangle to the region and combine it with\n * existing rectangles if possible.\n * The number of rectangles is forced to be less than 16\n * by combining excessive rectangles.\n */\nvoid Region::addRectangle (Rectangle *rPointer)\n{\n   container::typed::Iterator <Rectangle> it;\n   Rectangle *r = new Rectangle (rPointer->x, rPointer->y,\n      rPointer->width, rPointer->height);\n\n   for (it = rectangleList->iterator (); it.hasNext (); ) {\n      Rectangle *ownRect = it.getNext ();\n\n      int combinedHeight =\n         misc::max(r->y + r->height, ownRect->y + ownRect->height) -\n         misc::min(r->y, ownRect->y);\n      int combinedWidth =\n         misc::max(r->x + r->width, ownRect->x + ownRect->width) -\n         misc::min(r->x, ownRect->x);\n\n      if (rectangleList->size() >= 16 ||\n         combinedWidth * combinedHeight <=\n         ownRect->width * ownRect->height + r->width * r->height) {\n\n            r->x = misc::min(r->x, ownRect->x);\n            r->y = misc::min(r->y, ownRect->y);\n            r->width = combinedWidth;\n            r->height = combinedHeight;\n\n            rectangleList->removeRef (ownRect);\n      }\n   }\n\n   rectangleList->append (r);\n}\n\nContent::Type Content::maskForSelection (bool followReferences)\n{\n   Content::Type widgetMask = (Content::Type)\n      (Content::WIDGET_IN_FLOW |\n       (followReferences ? Content::WIDGET_OOF_REF : Content::WIDGET_OOF_CONT));\n   return (Content::Type)(Content::SELECTION_CONTENT | widgetMask);\n}\n\nvoid Content::intoStringBuffer(Content *content, misc::StringBuffer *sb)\n{\n   switch(content->type) {\n   case START:\n      sb->append (\"<start>\");\n      break;\n   case END:\n      sb->append (\"<end>\");\n      break;\n   case TEXT:\n      sb->append (\"\\\"\");\n      sb->append (content->text);\n      sb->append (\"\\\"\");\n      break;\n   case WIDGET_IN_FLOW:\n      sb->append (\"<widget in flow: \");\n      sb->appendPointer (content->widget);\n      sb->append (\" (\");\n      sb->append (content->widget->getClassName());\n      sb->append (\")>\");\n      break;\n   case WIDGET_OOF_REF:\n      sb->append (\"<widget oof ref: \");\n      sb->appendPointer (content->widgetReference->widget);\n      sb->append (\" (\");\n      sb->append (content->widgetReference->widget->getClassName());\n      sb->append (\", \");\n      sb->appendInt (content->widgetReference->parentRef);\n      sb->append (\")>\");\n      break;\n   case WIDGET_OOF_CONT:\n      sb->append (\"<widget oof cont: \");\n      sb->appendPointer (content->widget);\n      sb->append (\" (\");\n      sb->append (content->widget->getClassName());\n      sb->append (\")>\");\n      break;\n   case BREAK:\n      sb->append (\"<break>\");\n      break;\n   default:\n      sb->append (\"<\");\n      sb->appendInt (content->type);\n      sb->append (\"?>\");\n      break;\n   }\n}\n\nvoid Content::maskIntoStringBuffer(Type mask, misc::StringBuffer *sb)\n{\n   sb->append ((mask & START) ? \"st\" : \"--\");\n   sb->append (\":\");\n   sb->append ((mask & END) ? \"en\" : \"--\");\n   sb->append (\":\");\n   sb->append ((mask & TEXT) ? \"tx\" : \"--\");\n   sb->append (\":\");\n   sb->append ((mask & WIDGET_IN_FLOW) ? \"wf\" : \"--\");\n   sb->append (\":\");\n   sb->append ((mask & WIDGET_OOF_REF) ? \"Wr\" : \"--\");\n   sb->append (\":\");\n   sb->append ((mask & WIDGET_OOF_CONT) ? \"Wc\" : \"--\");\n   sb->append (\":\");\n   sb->append ((mask & BREAK) ? \"br\" : \"--\");\n}\n\nvoid Content::print (Content *content)\n{\n   misc::StringBuffer sb;\n   intoStringBuffer (content, &sb);\n   printf (\"%s\", sb.getChars ());\n}\n\nvoid Content::printMask (Type mask)\n{\n   misc::StringBuffer sb;\n   maskIntoStringBuffer (mask, &sb);\n   printf (\"%s\", sb.getChars ());\n}\n\n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/types.hh",
    "content": "#ifndef __DW_TYPES_HH__\n#define __DW_TYPES_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\nnamespace style {\n   class Style;\n}\n\nenum HPosition\n{\n   HPOS_LEFT,\n   HPOS_CENTER,\n   HPOS_RIGHT,\n   HPOS_INTO_VIEW, /* scroll only, until the content in question comes\n                    * into view */\n   HPOS_NO_CHANGE\n};\n\nenum VPosition\n{\n   VPOS_TOP,\n   VPOS_CENTER,\n   VPOS_BOTTOM,\n   VPOS_INTO_VIEW, /* scroll only, until the content in question comes\n                    * into view */\n   VPOS_NO_CHANGE\n};\n\nenum ScrollCommand {SCREEN_UP_CMD, SCREEN_DOWN_CMD, SCREEN_LEFT_CMD,\n                    SCREEN_RIGHT_CMD, LINE_UP_CMD, LINE_DOWN_CMD,\n                    LEFT_CMD, RIGHT_CMD, TOP_CMD, BOTTOM_CMD};\n\n/*\n * Different \"layers\" may be highlighted in a widget.\n */\nenum HighlightLayer\n{\n   HIGHLIGHT_SELECTION,\n   HIGHLIGHT_FINDTEXT,\n   HIGHLIGHT_NUM_LAYERS\n};\n\nstruct Point\n{\n  int x;\n  int y;\n};\n\n/**\n * \\brief Abstract interface for different shapes.\n */\nclass Shape: public lout::object::Object\n{\npublic:\n   virtual bool isPointWithin (int x, int y) = 0;\n   virtual void draw (core::View *view, core::style::Style *style, int x,\n                      int y) = 0;\n};\n\n/**\n * \\brief dw::core::Shape implemtation for simple rectangles.\n */\nclass Rectangle: public Shape\n{\npublic:\n   int x;\n   int y;\n   int width;\n   int height;\n\n   inline Rectangle () { }\n   Rectangle (int x, int y, int width, int height);\n\n   void draw (core::View *view, core::style::Style *style, int x, int y);\n   bool intersectsWith (Rectangle *otherRect, Rectangle *dest);\n   bool isSubsetOf (Rectangle *otherRect);\n   bool isPointWithin (int x, int y);\n   bool isEmpty () { return width <= 0 || height <= 0; };\n};\n\n/**\n * \\brief dw::core::Shape implemtation for simple circles.\n */\nclass Circle: public Shape\n{\npublic:\n   int x, y, radius;\n\n   Circle (int x, int y, int radius);\n\n   void draw (core::View *view, core::style::Style *style, int x, int y);\n   bool isPointWithin (int x, int y);\n};\n\n/**\n * \\brief dw::core::Shape implemtation for polygons.\n */\nclass Polygon: public Shape\n{\nprivate:\n   lout::misc::SimpleVector<Point> *points;\n   int minx, miny, maxx, maxy;\n\n   /**\n    * \\brief Return the z-coordinate of the vector product of two\n    *    vectors, whose z-coordinate is 0 (so that x and y of\n    *    the vector product is 0, too).\n    */\n   inline int zOfVectorProduct(int x1, int y1, int x2, int y2) {\n      return x1 * y2 - x2 * y1;\n   }\n\n   bool linesCross0(int ax1, int ay1, int ax2, int ay2,\n                    int bx1, int by1, int bx2, int by2);\n   bool linesCross(int ax1, int ay1, int ax2, int ay2,\n                   int bx1, int by1, int bx2, int by2);\n\npublic:\n   Polygon ();\n   ~Polygon ();\n\n   void draw (core::View *view, core::style::Style *style, int x, int y);\n   void addPoint (int x, int y);\n   bool isPointWithin (int x, int y);\n};\n\n/**\n * Implementation for a point set.\n * Currently represented as a set of rectangles not containing\n * each other.\n * It is guaranteed that the rectangles returned by rectangles ()\n * cover all rectangles that were added with addRectangle ().\n */\nclass Region\n{\nprivate:\n   lout::container::typed::List <Rectangle> *rectangleList;\n\npublic:\n   Region ();\n   ~Region ();\n\n   void clear () { rectangleList->clear (); };\n\n   void addRectangle (Rectangle *r);\n\n   lout::container::typed::Iterator <Rectangle> rectangles ()\n   {\n      return rectangleList->iterator ();\n   };\n};\n\n/**\n * \\brief Represents the allocation, i.e. actual position and size of a\n *    dw::core::Widget.\n */\nstruct Allocation\n{\n   int x;\n   int y;\n   int width;\n   int ascent;\n   int descent;\n};\n\nstruct Requisition\n{\n   int width;\n   int ascent;\n   int descent;\n};\n\nstruct Extremes\n{\n   int minWidth;\n   int maxWidth;\n   int minWidthIntrinsic;\n   int maxWidthIntrinsic;\n   int adjustmentWidth;\n};\n\nclass WidgetReference: public lout::object::Object\n{\npublic:\n   Widget *widget;\n   int parentRef;\n\n   WidgetReference (Widget *widget) { parentRef = -1; this->widget = widget; }\n};\n\nstruct Content\n{\n   enum Type {\n      START             = 1 << 0,\n      END               = 1 << 1,\n      TEXT              = 1 << 2,\n\n      /** \\brief widget in normal flow, so that _this_ widget\n          (containing this content) is both container (parent) and\n          generator */\n      WIDGET_IN_FLOW    = 1 << 3,\n\n      /** \\brief widget out of flow (OOF); _this_ widget (containing\n          this content) is only the container (parent), but _not_\n          generator */\n      WIDGET_OOF_CONT    = 1 << 4,\n\n      /** \\brief reference to a widget out of flow (OOF); _this_\n          widget (containing this content) is only the generator\n          (parent), but _not_ container */\n      WIDGET_OOF_REF    = 1 << 5,\n      BREAK             = 1 << 6,\n\n      /** \\brief can be used internally, but should never be exposed,\n          e. g. by iterators */\n      INVALID            = 1 << 7,\n\n      ALL               = 0xff,\n      REAL_CONTENT      = 0xff ^ (START | END),\n      SELECTION_CONTENT = TEXT | BREAK, // WIDGET_* must be set additionally\n      ANY_WIDGET        = WIDGET_IN_FLOW | WIDGET_OOF_CONT | WIDGET_OOF_REF,\n   };\n\n   /* Content is embedded in struct Word therefore we\n    * try to be space efficient.\n    */\n   short type;\n   bool space;\n   union {\n      const char *text;\n      Widget *widget;\n      WidgetReference *widgetReference;\n      int breakSpace;\n   };\n\n   static Content::Type maskForSelection (bool followReferences);\n\n   static void intoStringBuffer(Content *content, lout::misc::StringBuffer *sb);\n   static void maskIntoStringBuffer(Type mask, lout::misc::StringBuffer *sb);\n   static void print (Content *content);\n   static void printMask (Type mask);\n\n   inline Widget *getWidget () {\n      assert (type & ANY_WIDGET);\n      return type == WIDGET_OOF_REF ? widgetReference->widget : widget;\n   }\n};\n\n/**\n * \\brief Base class for dw::core::DrawingContext and\n *    dw::core::GettingWidgetAtPointContext.\n *\n * Not to be confused with the *stacking context* as defined by CSS.\n */\nclass StackingProcessingContext\n{\nprivate:\n   lout::container::typed::HashSet<lout::object::TypedPointer<Widget> >\n      *widgetsProcessedAsInterruption;\n\npublic:\n   inline StackingProcessingContext () {\n      widgetsProcessedAsInterruption =\n         new lout::container::typed::HashSet<lout::object::\n                                             TypedPointer<Widget> > (true);\n   }\n   \n   inline ~StackingProcessingContext ()\n   { delete widgetsProcessedAsInterruption; }\n\n   inline bool hasWidgetBeenProcessedAsInterruption (Widget *widget) {\n      lout::object::TypedPointer<Widget> key (widget);\n      return widgetsProcessedAsInterruption->contains (&key);\n   }\n\n   inline void addWidgetProcessedAsInterruption (Widget *widget) {\n      lout::object::TypedPointer<Widget> *key =\n         new lout::object::TypedPointer<Widget> (widget);\n      return widgetsProcessedAsInterruption->put (key);\n   }\n};\n\n/**\n * \\brief Set at the top when drawing.\n *\n * See \\ref dw-interrupted-drawing for details.\n */\nclass DrawingContext: public StackingProcessingContext\n{\nprivate:\n   Rectangle toplevelArea;\n\npublic:\n   inline DrawingContext (Rectangle *toplevelArea) {\n      this->toplevelArea = *toplevelArea;\n   }\n   \n   inline Rectangle *getToplevelArea () { return &toplevelArea; }\n};\n\n/**\n * \\brief Set at the top when getting the widget at the point.\n *\n * Similar to dw::core::DrawingContext.\n */\nclass GettingWidgetAtPointContext: public StackingProcessingContext\n{\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_TYPES_HH__\n"
  },
  {
    "path": "dw/ui.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"core.hh\"\n#include \"../lout/debug.hh\"\n\n#include <stdio.h>\n\nnamespace dw {\nnamespace core {\nnamespace ui {\n\nusing namespace lout;\nusing namespace lout::object;\n\nint Embed::CLASS_ID = -1;\n\nEmbed::Embed(Resource *resource)\n{\n   DBG_OBJ_CREATE (\"dw::core::ui::Embed\");\n   registerName (\"dw::core::ui::Embed\", &CLASS_ID);\n   this->resource = resource;\n   resource->setEmbed (this);\n   DBG_OBJ_ASSOC_CHILD (resource);\n}\n\nEmbed::~Embed()\n{\n   delete resource;\n   DBG_OBJ_DELETE ();\n}\n\nvoid Embed::sizeRequestSimpl (Requisition *requisition)\n{\n   resource->sizeRequest (requisition);\n   // TODO Correction should perhaps be left to the resouces.\n   correctRequisition(requisition, core::splitHeightPreserveAscent, true, true);\n}\n\nvoid Embed::getExtremesSimpl (Extremes *extremes)\n{\n   resource->getExtremes (extremes);\n   correctExtremes (extremes, false);\n   extremes->adjustmentWidth =\n      misc::min (extremes->minWidthIntrinsic, extremes->minWidth);\n}\n\nvoid Embed::sizeAllocateImpl (Allocation *allocation)\n{\n   resource->sizeAllocate (allocation);\n}\n\nint Embed::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   return resource->getAvailWidthOfChild (child, forceValue);\n}\n\nint Embed::getAvailHeightOfChild (Widget *child, bool forceValue)\n{\n   return resource->getAvailHeightOfChild (child, forceValue);\n}\n\nvoid Embed::correctRequisitionOfChild (Widget *child,\n                                       Requisition *requisition,\n                                       void (*splitHeightFun) (int, int*, int*),\n                                       bool allowDecreaseWidth,\n                                       bool allowDecreaseHeight)\n{\n   resource->correctRequisitionOfChild (child, requisition, splitHeightFun,\n                                        allowDecreaseWidth,\n                                        allowDecreaseHeight);\n}\n\nvoid Embed::correctExtremesOfChild (Widget *child, Extremes *extremes,\n                                       bool useAdjustmentWidth)\n{\n   resource->correctExtremesOfChild (child, extremes, useAdjustmentWidth);\n}\n\nvoid Embed::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n   resource->containerSizeChangedForChildren ();\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Embed::enterNotifyImpl (core::EventCrossing *event)\n{\n   resource->emitEnter();\n   Widget::enterNotifyImpl(event);\n}\n\nvoid Embed::leaveNotifyImpl (core::EventCrossing *event)\n{\n   resource->emitLeave();\n   Widget::leaveNotifyImpl(event);\n}\n\nbool Embed::buttonPressImpl (core::EventButton *event)\n{\n   bool handled;\n\n   if (event->button == 3) {\n      resource->emitClicked(event);\n      handled = true;\n   } else {\n      handled = false;\n   }\n   return handled;\n}\n\nvoid Embed::setDisplayed (bool displayed)\n{\n   resource->setDisplayed (displayed);\n}\n\nvoid Embed::setEnabled (bool enabled)\n{\n   resource->setEnabled (enabled);\n}\n\nvoid Embed::draw (View *view, Rectangle *area, DrawingContext *context)\n{\n   drawWidgetBox (view, area, false);\n   resource->draw (view, area, context);\n}\n\nIterator *Embed::iterator (Content::Type mask, bool atEnd)\n{\n   return resource->iterator (mask, atEnd);\n}\n\nvoid Embed::setStyle (style::Style *style)\n{\n   resource->setStyle (style);\n   Widget::setStyle (style);\n}\n\n// ----------------------------------------------------------------------\n\nbool Resource::ActivateEmitter::emitToReceiver (lout::signal::Receiver\n                                                *receiver,\n                                                int signalNo,\n                                                int argc, Object **argv)\n{\n   ActivateReceiver *ar = (ActivateReceiver*)receiver;\n   Resource *res = (Resource*)((Pointer*)argv[0])->getValue ();\n\n   switch (signalNo) {\n   case 0:\n      ar->activate (res);\n      break;\n   case 1:\n      ar->enter (res);\n      break;\n   case 2:\n      ar->leave (res);\n      break;\n   default:\n      misc::assertNotReached ();\n   }\n   return false;\n}\n\nvoid Resource::ActivateEmitter::emitActivate (Resource *resource)\n{\n   Pointer p (resource);\n   Object *argv[1] = { &p };\n   emitVoid (0, 1, argv);\n}\n\nvoid Resource::ActivateEmitter::emitEnter (Resource *resource)\n{\n   Pointer p (resource);\n   Object *argv[1] = { &p };\n   emitVoid (1, 1, argv);\n}\n\nvoid Resource::ActivateEmitter::emitLeave (Resource *resource)\n{\n   Pointer p (resource);\n   Object *argv[1] = { &p };\n   emitVoid (2, 1, argv);\n}\n\n// ----------------------------------------------------------------------\n\nResource::~Resource ()\n{\n   DBG_OBJ_DELETE ();\n}\n\nvoid Resource::setEmbed (Embed *embed)\n{\n   this->embed = embed;\n}\n\nvoid Resource::getExtremes (Extremes *extremes)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"getExtremes\");\n\n   /* Simply return the requisition width */\n   Requisition requisition;\n   sizeRequest (&requisition);\n   extremes->minWidth = extremes->maxWidth = requisition.width;\n   extremes->minWidthIntrinsic = extremes->minWidth;\n   extremes->maxWidthIntrinsic = extremes->maxWidth;\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d / %d\",\n                 extremes->minWidth, extremes->maxWidth);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Resource::sizeAllocate (Allocation *allocation)\n{\n}\n\nint Resource::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   // Only used when the resource contains other dillo widgets.\n   misc::notImplemented (\"Resource::getAvailWidthOfChild\");\n   return 0;\n}\n\nint Resource::getAvailHeightOfChild (Widget *child, bool forceValue)\n{\n   // Only used when the resource contains other dillo widgets.\n   misc::notImplemented (\"Resource::getAvailHeightOfChild\");\n   return 0;\n}\n\nvoid Resource::correctRequisitionOfChild (Widget *child,\n                                          Requisition *requisition,\n                                          void (*splitHeightFun) (int, int*,\n                                                                  int*),\n                                          bool allowDecreaseWidth,\n                                          bool allowDecreaseHeight)\n{\n   // Only used when the resource contains other dillo widgets.\n   misc::notImplemented (\"Resource::correctRequisitionOfChild\");\n}\n\nvoid Resource::correctExtremesOfChild (Widget *child, Extremes *extremes,\n                                       bool useAdjustmentWidth)\n{\n   // Only used when the resource contains other dillo widgets.\n   misc::notImplemented (\"Resource::correctExtremesOfChild\");\n}\n\nvoid Resource::containerSizeChangedForChildren ()\n{\n   // No children by default.\n}\n\nvoid Resource::setDisplayed (bool displayed)\n{\n}\n\nvoid Resource::draw (View *view, Rectangle *area, DrawingContext *context)\n{\n}\n\nvoid Resource::setStyle (style::Style *style)\n{\n}\n\nvoid Resource::emitEnter ()\n{\n   activateEmitter.emitEnter(this);\n}\n\nvoid Resource::emitLeave ()\n{\n   activateEmitter.emitLeave(this);\n}\n\nbool Resource::ClickedEmitter::emitToReceiver(lout::signal::Receiver *receiver,\n                                              int signalNo, int argc,\n                                              Object **argv)\n{\n   ((ClickedReceiver*)receiver)\n      ->clicked ((Resource*)((Pointer*)argv[0])->getValue (),\n                 (EventButton*)((Pointer*)argv[1])->getValue());\n   return false;\n}\n\nvoid Resource::ClickedEmitter::emitClicked (Resource *resource,\n                                            EventButton *event)\n{\n   Pointer p1 (resource);\n   Pointer p2 (event);\n   Object *argv[2] = { &p1, &p2 };\n   emitVoid (0, 2, argv);\n}\n\n// ----------------------------------------------------------------------\n\nIterator *LabelButtonResource::iterator (Content::Type mask, bool atEnd)\n{\n   /** \\todo Perhaps in brackets? */\n   // return new TextIterator (getEmbed (), mask, atEnd, getLabel ());\n   /** \\bug Not implemented. */\n   return new EmptyIterator (getEmbed (), mask, atEnd);\n}\n\n// ----------------------------------------------------------------------\n\nvoid ComplexButtonResource::LayoutReceiver::resizeQueued (bool extremesChanged)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"LayoutReceiver::resizeQueued\", \"%s\",\n                  extremesChanged ? \"true\" : \"false\");\n   resource->queueResize (extremesChanged);\n   DBG_OBJ_LEAVE ();\n}\n\nComplexButtonResource::ComplexButtonResource ()\n{\n   DBG_OBJ_CREATE (\"dw::core::ui::ComplexButtonResource\");\n   layout = NULL;\n   layoutReceiver.resource = this;\n   click_x = click_y = -1;\n}\n\nvoid ComplexButtonResource::init (Widget *widget)\n{\n   childWidget = widget;\n\n   layout = new Layout (createPlatform ());\n   setLayout (layout);\n   DBG_OBJ_ASSOC_CHILD (layout);\n   layout->setWidget (widget);\n   layout->connect (&layoutReceiver);\n\n   if (getEmbed ())\n      childWidget->setQuasiParent (getEmbed ());\n}\n\nvoid ComplexButtonResource::setEmbed (Embed *embed)\n{\n   ButtonResource::setEmbed (embed);\n\n   if (childWidget)\n      childWidget->setQuasiParent (getEmbed ());\n}\n\nComplexButtonResource::~ComplexButtonResource ()\n{\n   delete layout;\n   DBG_OBJ_DELETE ();\n}\n\nvoid ComplexButtonResource::sizeRequest (Requisition *requisition)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"sizeRequest\");\n\n   Requisition widgetRequisition;\n   childWidget->sizeRequest (&widgetRequisition);\n   requisition->width = widgetRequisition.width + 2 * reliefXThickness ();\n   requisition->ascent = widgetRequisition.ascent + reliefYThickness ();\n   requisition->descent = widgetRequisition.descent + reliefYThickness ();\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d * (%d + %d)\",\n                 requisition->width, requisition->ascent, requisition->descent);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid ComplexButtonResource::getExtremes (Extremes *extremes)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"getExtremes\");\n\n   Extremes widgetExtremes;\n   childWidget->getExtremes (&widgetExtremes);\n   extremes->minWidth = widgetExtremes.minWidth + 2 * reliefXThickness ();\n   extremes->maxWidth = widgetExtremes.maxWidth + 2 * reliefXThickness ();\n   extremes->minWidthIntrinsic = extremes->minWidth;\n   extremes->maxWidthIntrinsic = extremes->maxWidth;\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"result: %d / %d\",\n                 extremes->minWidth, extremes->maxWidth);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid ComplexButtonResource::sizeAllocate (Allocation *allocation)\n{\n}\n\nint ComplexButtonResource::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   int embedWidth = getEmbed()->getAvailWidth (forceValue);\n   if (embedWidth == -1)\n      return -1;\n   else\n      return misc::max (embedWidth - 2 * reliefXThickness (), 0);\n}\n\nint ComplexButtonResource::getAvailHeightOfChild (Widget *child,\n                                                  bool forceValue)\n{\n   int embedHeight = getEmbed()->getAvailHeight (forceValue);\n   if (embedHeight == -1)\n      return -1;\n   else\n      return misc::max (embedHeight - 2 * reliefYThickness (), 0);\n}\n\nvoid ComplexButtonResource::correctRequisitionOfChild (Widget *child,\n                                                       Requisition *requisition,\n                                                       void (*splitHeightFun)\n                                                       (int, int*, int*),\n                                                       bool allowDecreaseWidth,\n                                                       bool allowDecreaseHeight)\n{\n   // Similar to Widget::correctRequisitionOfChild, but for percentage\n   // the relief has to be considered.\n\n   if (style::isPerLength (child->getStyle()->width)) {\n      int availWidth = getEmbed()->getAvailHeight (false);\n      if (availWidth != -1) {\n         int baseWidth = misc::max (availWidth\n                                    - getEmbed()->boxDiffWidth ()\n                                    - 2 * reliefXThickness (),\n                                    0);\n         int newWidth =\n            child->applyPerWidth (baseWidth, child->getStyle()->width);\n         requisition->width = allowDecreaseWidth ?\n            newWidth : misc::max (requisition->width, newWidth);\n      }\n   } else\n      getEmbed()->correctReqWidthOfChildNoRec (child, requisition,\n                                               allowDecreaseWidth);\n\n   // TODO Percentage heights are ignored again.\n   getEmbed()->correctReqHeightOfChildNoRec(child, requisition,\n                                            splitHeightFun, allowDecreaseWidth);\n\n}\n\nvoid ComplexButtonResource::correctExtremesOfChild (Widget *child,\n                                                    Extremes *extremes,\n                                                    bool useAdjustmentWidth)\n{\n   // Similar to Widget::correctExtremesOfChild, but for percentage\n   // the relief has to be considered.\n\n   if (style::isPerLength (child->getStyle()->width)) {\n      int availWidth = getEmbed()->getAvailHeight (false);\n      if (availWidth != -1) {\n         int baseWidth = misc::max (availWidth\n                                    - getEmbed()->boxDiffWidth ()\n                                    - 2 * reliefXThickness (),\n                                    0);\n         extremes->minWidth = extremes->maxWidth =\n            child->applyPerWidth (baseWidth, child->getStyle()->width);\n      }\n   } else\n      getEmbed()->correctExtremesOfChildNoRec (child, extremes,\n                                               useAdjustmentWidth);\n}\n\nvoid ComplexButtonResource::containerSizeChangedForChildren ()\n{\n   layout->containerSizeChanged ();\n}\n\nIterator *ComplexButtonResource::iterator (Content::Type mask, bool atEnd)\n{\n   /**\n    * \\bug Implementation.\n    * This is a bit more complicated: We have two layouts here.\n    */\n   return new EmptyIterator (getEmbed (), mask, atEnd);\n}\n\n// ----------------------------------------------------------------------\n\nIterator *TextResource::iterator (Content::Type mask, bool atEnd)\n{\n   // return new TextIterator (getEmbed (), mask, atEnd, getText ());\n   /** \\bug Not implemented. */\n   return new EmptyIterator (getEmbed (), mask, atEnd);\n}\n\n// ----------------------------------------------------------------------\n\nIterator *CheckButtonResource::iterator (Content::Type mask, bool atEnd)\n{\n   //return new TextIterator (getEmbed (), mask, atEnd,\n   //                         isActivated () ? \"[X]\" : \"[ ]\");\n   /** \\bug Not implemented. */\n   return new EmptyIterator (getEmbed (), mask, atEnd);\n}\n\n// ----------------------------------------------------------------------\n\nRadioButtonResource::GroupIterator::~GroupIterator ()\n{\n}\n\nIterator *RadioButtonResource::iterator (Content::Type mask, bool atEnd)\n{\n   //return new TextIterator (getEmbed (), mask, atEnd,\n   //                         isActivated () ? \"(*)\" : \"( )\");\n   /** \\bug Not implemented. */\n   return new EmptyIterator (getEmbed (), mask, atEnd);\n}\n\n} // namespace ui\n} // namespace core\n} // namespace dw\n\n"
  },
  {
    "path": "dw/ui.hh",
    "content": "#ifndef __DW_UI_HH__\n#define __DW_UI_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief Anything related to embedded UI widgets is defined here.\n *\n * UI resources are another abstraction for Dw widgets, which are not\n * fully implemented in a platform-independent way. Typically, they\n * involve creating widgets, which the underlying UI toolkit provides.\n *\n * As you see in this diagram:\n *\n * \\dot\n * digraph G {\n *    node [shape=record, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"none\", arrowtail=\"empty\", dir=\"both\",\n *          labelfontname=Helvetica, labelfontsize=10, color=\"#404040\",\n *          labelfontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *\n *    subgraph cluster_core {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"dw::core\";\n *\n *       subgraph cluster_ui {\n *          style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *          label=\"dw::core::ui\";\n *\n *          Embed [URL=\"\\ref dw::core::ui::Embed\"];\n *          Resource [color=\"#a0a0a0\", URL=\"\\ref dw::core::ui::Resource\"];\n *          LabelButtonResource [color=\"#a0a0a0\",\n *                              URL=\"\\ref dw::core::ui::LabelButtonResource\"];\n *          EntryResource [color=\"#a0a0a0\",\n *                        URL=\"\\ref dw::core::ui::EntryResource\"];\n *          etc [color=\"#a0a0a0\", label=\"...\"];\n *       }\n *\n *       Widget [URL=\"\\ref dw::core::Widget\", color=\"#a0a0a0\"];\n *    }\n *\n *    subgraph cluster_fltk {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"dw::fltk::ui\";\n *\n *       FltkLabelButtonResource\n *          [URL=\"\\ref dw::fltk::ui::FltkLabelButtonResource\"];\n *       FltkEntryResource [URL=\"\\ref dw::fltk::ui::FltkEntryResource\"];\n *    }\n *\n *    Widget -> Embed;\n *    Embed -> Resource [arrowhead=\"open\", arrowtail=\"none\",\n *                       headlabel=\"1\", taillabel=\"1\"];\n *    Resource -> LabelButtonResource;\n *    Resource -> EntryResource;\n *    Resource -> etc;\n *    LabelButtonResource -> FltkLabelButtonResource;\n *    EntryResource -> FltkEntryResource;\n * }\n * \\enddot\n *\n * <center>[\\ref uml-legend \"legend\"]</center>\n *\n * there are several levels:\n *\n * <ol>\n * <li> The Dw widget is dw::core::ui::Embed. It delegates most to\n *      dw::core::ui::Resource, which has similar methods like\n *      dw::core::Widget.\n *\n * <li> There are several sub interfaces of dw::core::ui::Resource, which\n *      may provide methods, as e.g. dw::core::ui::ListResource::addItem. In a\n *      platform independent context, you can cast the result of\n *      dw::core::ui::Embed::getResource to a specific sub class, if you\n *      know, which one is used. E.g., if you know, that a given instance\n *      dw::core::ui::Embed refers to a dw::core::ui::ListResource, you can\n *      write something like:\n *\n * \\code\n * dw::core::ui::Embed *embed;\n * //...\n * ((dw::core::ui::ListResource*)embed->getResource ())->addItem (\"Hello!\");\n * \\endcode\n *\n * <li> These sub classes are then fully implemented in a platform specific\n *      way. For an example, look at dw::fltk::ui.\n * </ol>\n *\n * There is a factory interface, dw::core::ui::ResourceFactory, which\n * provides methods for creating common resources. By calling\n * dw::core::Layout::getResourceFactory, which calls\n * dw::core::Platform::getResourceFactory, you get the factory for the used\n * platform.\n *\n * It is possible to define additional sub classes of\n * dw::core::ui::Resource, but since they are not provided by\n * dw::core::ui::ResourceFactory, you have to define some other\n * abstractions, if you want to remain platform independent.\n *\n *\n * <h3>...</h3>\n *\n *\n * <h3>Resouces needed for HTML</h3>\n *\n * This chapter describes, how the form controls defined by HTML are\n * implemented in Dw. Some of them do not refer to UI resources, but to\n * other widgets, links to the respective documentations are provided\n * here.\n *\n * <h4>Resouces created with \\<INPUT\\></h4>\n *\n * The HTML \\<INPUT\\> is always implemented by using UI\n * resources. \\<INPUT\\> element has the following attributes:\n *\n * <table>\n * <tr><th>Attribute <th>Implementation\n * <tr><td>type      <td>This defines the resource you have to instantiate.\n * <tr><td>name      <td>Not needed within Dw.\n * <tr><td>value     <td>The initial value is treated differently by different\n *                       resources.\n * <tr><td>checked   <td>Parameter to\n *                     dw::core::ui::ResourceFactory::createCheckButtonResource\n *                and dw::core::ui::ResourceFactory::createRadioButtonResource.\n * <tr><td>disabled  <td>This is provided for all resources by\n *                       dw::core::ui::Resource::setEnabled.\n * <tr><td>readonly  <td>This is provided by\n *                       dw::core::ui::TextResource::setEditable.\n * <tr><td>size      <td>This is handled by styles.\n * <tr><td>maxlength <td>Parameter of\n *                       dw::core::ui::ResourceFactory::createEntryResource.\n * <tr><td>src       <td>Handled by the caller (HTML parser).\n * <tr><td>alt       <td>Handled by the caller (HTML parser).\n * <tr><td>usemap    <td>Handled by the caller (HTML parser).\n * <tr><td>ismap     <td>Handled by the caller (HTML parser).\n * <tr><td>tabindex  <td>Not supported currently.\n * <tr><td>accesskey <td>Not supported currently.\n * <tr><td>onfocus   <td>Not supported currently.\n * <tr><td>onblur    <td>Not supported currently.\n * <tr><td>onselect  <td>Not supported currently.\n * <tr><td>onchange  <td>Not supported currently.\n * <tr><td>accept    <td>Not supported currently.\n * </table>\n *\n * For the different values of \\em type, the following resources can be\n * used:\n *\n * <table>\n * <tr><th>Type     <th>Resource\n *                  <th>Factory Method\n * <tr><td>text     <td>dw::core::ui::EntryResource\n *                  <td>dw::core::ui::ResourceFactory::createEntryResource\n * <tr><td>password <td>dw::core::ui::EntryResource\n *                  <td>dw::core::ui::ResourceFactory::createEntryResource\n * <tr><td>checkbox <td>dw::core::ui::CheckButtonResource\n *                 <td>dw::core::ui::ResourceFactory::createCheckButtonResource\n * <tr><td>radio    <td>dw::core::ui::RadioButtonResource\n *                 <td>dw::core::ui::ResourceFactory::createRadioButtonResource\n * <tr><td>submit   <td>dw::core::ui::LabelButtonResource\n *                 <td>dw::core::ui::ResourceFactory::createLabelButtonResource\n * <tr><td>image    <td>dw::core::ui::ComplexButtonResource\n *              <td>dw::core::ui::ResourceFactory::createComplexButtonResource,\n *                     width a dw::Image inside and relief = false.\n * <tr><td>reset    <td>dw::core::ui::LabelButtonResource\n *                 <td>dw::core::ui::ResourceFactory::createLabelButtonResource\n * <tr><td>button   <td>dw::core::ui::LabelButtonResource\n *                 <td>dw::core::ui::ResourceFactory::createLabelButtonResource\n * <tr><td>hidden   <td>No rendering necessary.\n *                  <td>-\n * <tr><td>file     <td>Not supported currently.\n *                  <td>-\n * </table>\n *\n * <h4>\\<SELECT\\>, \\<OPTGROUP\\>, and \\<OPTION\\></h4>\n *\n * \\<SELECT\\> is implemented either by dw::core::ui::OptionMenuResource\n * (better suitable for \\em size = 1 and single selection) or\n * dw::core::ui::ListResource, which have a common base,\n * dw::core::ui::SelectionResource. In the latter case, \\em size must be\n * specified via dw::core::style::Style.\n *\n * Factory methods are dw::core::ui::ResourceFactory::createListResource and\n * dw::core::ui::ResourceFactory::createOptionMenuResource.\n *\n * \\<OPTION\\>'s are added via dw::core::ui::SelectionResource::addItem.\n *\n * \\<OPTGROUP\\> are created by using dw::core::ui::SelectionResource::pushGroup\n * and dw::core::ui::SelectionResource::popGroup.\n *\n * For lists, the selection mode must be set in\n * dw::core::ui::ResourceFactory::createListResource.\n *\n * <h4>\\<TEXTAREA\\></h4>\n *\n * \\<TEXTAREA\\> is implemented by dw::core::ui::MultiLineTextResource,\n * the factory method is\n * dw::core::ui::ResourceFactory::createMultiLineTextResource.\n * dw::core::ui::TextResource::setEditable can be used, as for entries.\n *\n * <h4>\\<BUTTON\\></h4>\n *\n * For handling \\<BUTTON\\>, dw::core::ui::ComplexButtonResource should be used,\n * with a dw::Textblock inside, and relief = true. The contents of \\<BUTTON\\>\n * is then added to the dw::Textblock.\n *\n * \\todo describe activation signal\n */\nnamespace ui {\n\nclass Resource;\n\n/**\n * \\brief A widget for embedding UI widgets.\n *\n * \\sa dw::core::ui\n */\nclass Embed: public Widget\n{\n   friend class Resource;\n\nprivate:\n   Resource *resource;\n\nprotected:\n   void sizeRequestSimpl (Requisition *requisition);\n   void getExtremesSimpl (Extremes *extremes);\n   void sizeAllocateImpl (Allocation *allocation);\n\n   int getAvailWidthOfChild (Widget *child, bool forceValue);\n   int getAvailHeightOfChild (Widget *child, bool forceValue);\n   void correctRequisitionOfChild (Widget *child,\n                                   Requisition *requisition,\n                                   void (*splitHeightFun) (int, int*, int*),\n                                   bool allowDecreaseWidth,\n                                   bool allowDecreaseHeight);\n   void correctExtremesOfChild (Widget *child, Extremes *extremes,\n                                bool useAdjustmentWidth);\n\n   void containerSizeChangedForChildren ();\n\n   void enterNotifyImpl (core::EventCrossing *event);\n   void leaveNotifyImpl (core::EventCrossing *event);\n   bool buttonPressImpl (core::EventButton *event);\n\npublic:\n   static int CLASS_ID;\n\n   Embed(Resource *resource);\n   ~Embed();\n\n   void setDisplayed (bool displayed);\n   void setEnabled (bool enabled);\n   void draw (View *view, Rectangle *area, DrawingContext *context);\n   Iterator *iterator (Content::Type mask, bool atEnd);\n   void setStyle (style::Style *style);\n\n   inline Resource *getResource () { return resource; }\n\n   inline void correctReqWidthOfChildNoRec (Widget *child,\n                                            Requisition *requisition,\n                                            bool allowDecreaseWidth)\n   { Widget::correctReqWidthOfChild (child, requisition, allowDecreaseWidth); }\n\n   inline void correctReqHeightOfChildNoRec (Widget *child,\n                                             Requisition *requisition,\n                                             void (*splitHeightFun) (int, int*,\n                                                                     int*),\n                                             bool allowDecreaseHeight)\n   { Widget::correctReqHeightOfChild (child, requisition, splitHeightFun,\n                                      allowDecreaseHeight); }\n\n   virtual void correctExtremesOfChildNoRec (Widget *child, Extremes *extremes,\n                                             bool useAdjustmentWidth)\n   { Widget::correctExtremesOfChild (child, extremes, useAdjustmentWidth); }\n};\n\n/**\n * \\brief Basic interface for all resources.\n *\n * \\sa dw::core::ui\n */\nclass Resource\n{\n   friend class Embed;\n\npublic:\n   /**\n    * \\brief Receiver interface for the \"activate\" signal.\n    */\n   class ActivateReceiver: public lout::signal::Receiver\n   {\n   public:\n      virtual void activate (Resource *resource) = 0;\n      virtual void enter (Resource *resource) = 0;\n      virtual void leave (Resource *resource) = 0;\n   };\n   /**\n    * \\brief Receiver interface for the \"clicked\" signal.\n    */\n   class ClickedReceiver: public lout::signal::Receiver\n   {\n   public:\n      virtual void clicked (Resource *resource, EventButton *event) = 0;\n   };\n\nprivate:\n   class ActivateEmitter: public lout::signal::Emitter\n   {\n   protected:\n      bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,\n                           int argc, Object **argv);\n   public:\n      inline void connectActivate (ActivateReceiver *receiver) {\n         connect (receiver); }\n      void emitActivate (Resource *resource);\n      void emitEnter (Resource *resource);\n      void emitLeave (Resource *resource);\n   };\n\n   class ClickedEmitter: public lout::signal::Emitter\n   {\n   protected:\n      bool emitToReceiver (lout::signal::Receiver *receiver, int signalNo,\n                           int argc, Object **argv);\n   public:\n      inline void connectClicked (ClickedReceiver *receiver) {\n         connect (receiver); }\n      void emitClicked (Resource *resource, EventButton *event);\n   };\n\n   Embed *embed;\n   ActivateEmitter activateEmitter;\n   ClickedEmitter clickedEmitter;\n\n   void emitEnter ();\n   void emitLeave ();\nprotected:\n   inline void queueResize (bool extremesChanged) {\n      if (embed) embed->queueResize (0, extremesChanged);\n   }\n\n   virtual Embed *getEmbed () { return embed; }\n   virtual void setEmbed (Embed *embed);\n\n   inline void emitActivate () {\n      return activateEmitter.emitActivate (this); }\n   inline void emitClicked (EventButton *event) {\n      clickedEmitter.emitClicked (this, event); }\n\npublic:\n   inline Resource ()\n   { embed = NULL; DBG_OBJ_CREATE (\"dw::core::ui::Resource\"); }\n\n   virtual ~Resource ();\n\n   virtual void sizeRequest (Requisition *requisition) = 0;\n   virtual void getExtremes (Extremes *extremes);\n   virtual void sizeAllocate (Allocation *allocation);\n\n   virtual int getAvailWidthOfChild (Widget *child, bool forceValue);\n   virtual int getAvailHeightOfChild (Widget *child, bool forceValue);\n   virtual void correctRequisitionOfChild (Widget *child,\n                                           Requisition *requisition,\n                                           void (*splitHeightFun) (int, int*,\n                                                                   int*),\n                                           bool allowDecreaseWidth,\n                                           bool allowDecreaseHeight);\n   virtual void correctExtremesOfChild (Widget *child, Extremes *extremes,\n                                        bool useAdjustmentWidth);\n   virtual void containerSizeChangedForChildren ();\n\n   virtual void setDisplayed (bool displayed);\n   virtual void draw (View *view, Rectangle *area, DrawingContext *context);\n   virtual Iterator *iterator (Content::Type mask, bool atEnd) = 0;\n   virtual void setStyle (style::Style *style);\n\n   virtual bool isEnabled () = 0;\n   virtual void setEnabled (bool enabled) = 0;\n\n   inline void connectActivate (ActivateReceiver *receiver) {\n      activateEmitter.connectActivate (receiver); }\n   inline void connectClicked (ClickedReceiver *receiver) {\n      clickedEmitter.connectClicked (receiver); }\n};\n\n\nclass ButtonResource: public Resource\n{};\n\n/**\n * \\brief Interface for labelled buttons resources.\n */\nclass LabelButtonResource: public ButtonResource\n{\npublic:\n   Iterator *iterator (Content::Type mask, bool atEnd);\n\n   virtual const char *getLabel () = 0;\n   virtual void setLabel (const char *label) = 0;\n};\n\nclass ComplexButtonResource: public ButtonResource\n{\nprivate:\n   class LayoutReceiver: public Layout::Receiver\n   {\n   public:\n      ComplexButtonResource *resource;\n\n      void resizeQueued (bool extremesChanged);\n   };\n\n   friend class LayoutReceiver;\n   LayoutReceiver layoutReceiver;\n\n   Widget *childWidget;\n\nprotected:\n   Layout *layout;\n   int click_x, click_y;\n\n   void setEmbed (Embed *embed);\n\n   virtual Platform *createPlatform () = 0;\n   virtual void setLayout (Layout *layout) = 0;\n\n   virtual int reliefXThickness () = 0;\n   virtual int reliefYThickness () = 0;\n\n   void init (Widget *widget);\n\npublic:\n   ComplexButtonResource ();\n   ~ComplexButtonResource ();\n\n   void sizeRequest (Requisition *requisition);\n   void getExtremes (Extremes *extremes);\n   void sizeAllocate (Allocation *allocation);\n\n   int getAvailWidthOfChild (Widget *child, bool forceValue);\n   int getAvailHeightOfChild (Widget *child, bool forceValue);\n   void correctRequisitionOfChild (Widget *child,\n                                   Requisition *requisition,\n                                   void (*splitHeightFun) (int, int*, int*),\n                                   bool allowDecreaseWidth,\n                                   bool allowDecreaseHeight);\n   void correctExtremesOfChild (Widget *child, Extremes *extremes,\n                                bool useAdjustmentWidth);\n   void containerSizeChangedForChildren ();\n\n   Iterator *iterator (Content::Type mask, bool atEnd);\n   int getClickX () {return click_x;};\n   int getClickY () {return click_y;};\n};\n\n/**\n * \\brief Base interface for dw::core::ui::ListResource and\n *    dw::core::ui::OptionMenuResource.\n */\nclass SelectionResource: public Resource\n{\npublic:\n   virtual void addItem (const char *str, bool enabled, bool selected) = 0;\n   virtual void setItem (int index, bool selected) = 0;\n   virtual void pushGroup (const char *name, bool enabled) = 0;\n   virtual void popGroup () = 0;\n\n   virtual int getNumberOfItems () = 0;\n   virtual bool isSelected (int index) = 0;\n};\n\nclass ListResource: public SelectionResource\n{\npublic:\n   enum SelectionMode {\n      /**\n       * \\brief Exactly one item is selected.\n       *\n       * If no item is selected initially, the first one is selected.\n       */\n      SELECTION_EXACTLY_ONE,\n\n      /**\n       * \\brief Exactly one item is selected, except possibly at the beginning.\n       *\n       * If no item is selected initially, no one is selected automatically.\n       * The user may not unselect the only selected item.\n       */\n      SELECTION_EXACTLY_ONE_BY_USER,\n\n      /**\n       * \\brief At most one item is selected.\n       *\n       * If no item is selected initially, no one is selected automatically.\n       * The user may unselect the only selected item.\n       */\n      SELECTION_AT_MOST_ONE,\n\n      /**\n       * \\brief An arbitrary number of items may be selected.\n       */\n      SELECTION_MULTIPLE\n   };\n};\n\nclass OptionMenuResource: public SelectionResource\n{\n};\n\nclass TextResource: public Resource\n{\npublic:\n   Iterator *iterator (Content::Type mask, bool atEnd);\n\n   virtual const char *getText () = 0;\n   virtual void setText (const char *text) = 0;\n   virtual bool isEditable () = 0;\n   virtual void setEditable (bool editable) = 0;\n};\n\nclass EntryResource: public TextResource\n{\npublic:\n   enum { UNLIMITED_SIZE = -1 };\n   virtual void setMaxLength (int maxlen) = 0;\n};\n\nclass MultiLineTextResource: public TextResource\n{\n};\n\n\nclass ToggleButtonResource: public Resource\n{\npublic:\n   virtual bool isActivated () = 0;\n   virtual void setActivated (bool activated) = 0;\n};\n\nclass CheckButtonResource: public ToggleButtonResource\n{\npublic:\n   Iterator *iterator (Content::Type mask, bool atEnd);\n};\n\nclass RadioButtonResource: public ToggleButtonResource\n{\npublic:\n   class GroupIterator\n   {\n   protected:\n      GroupIterator () { }\n      virtual ~GroupIterator ();\n\n   public:\n      virtual bool hasNext () = 0;\n      virtual RadioButtonResource *getNext () = 0;\n      virtual void unref () = 0;\n   };\n\n   /**\n    * \\brief Return an iterator, to access all radio button resources\n    *    within the group.\n    */\n   virtual GroupIterator *groupIterator () = 0;\n\n   Iterator *iterator (Content::Type mask, bool atEnd);\n};\n\n\n/**\n * \\brief A factory for the common resource.\n */\nclass ResourceFactory: public lout::object::Object\n{\npublic:\n   virtual LabelButtonResource *createLabelButtonResource (const char *label)\n      = 0;\n   virtual ComplexButtonResource *createComplexButtonResource (Widget *widget,\n                                                               bool relief)\n      = 0;\n   virtual ListResource *createListResource (ListResource::SelectionMode\n                                             selectionMode, int rows) = 0;\n   virtual OptionMenuResource *createOptionMenuResource () = 0;\n   virtual EntryResource *createEntryResource (int size, bool password,\n                                               const char *label,\n                                               const char *placeholder) = 0;\n   virtual MultiLineTextResource *createMultiLineTextResource (int cols,\n                                                               int rows,\n                                                  const char *placeholder) = 0;\n   virtual CheckButtonResource *createCheckButtonResource (bool activated) = 0;\n   virtual RadioButtonResource *createRadioButtonResource (RadioButtonResource\n                                                           *groupedWith,\n                                                           bool activated) = 0;\n};\n\n} // namespace ui\n} // namespace core\n} // namespace dw\n\n#endif // __DW_UI_HH__\n"
  },
  {
    "path": "dw/view.hh",
    "content": "#ifndef __DW_VIEW_HH__\n#define __DW_VIEW_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief An interface to encapsulate platform dependent drawing.\n *\n * \\sa\\ref dw-overview, \\ref dw-layout-views\n */\nclass View: public lout::object::Object\n{\npublic:\n   /*\n    * ----------------------------\n    *    Operations on the view\n    * ----------------------------\n    */\n\n   /**\n    * \\brief This methods notifies the view, that it has been attached to a\n    *    layout.\n    */\n   virtual void setLayout (Layout *layout) = 0;\n\n   /**\n    * \\brief Set the canvas size.\n    */\n   virtual void setCanvasSize (int width, int ascent, int descent) = 0;\n\n   /**\n    * \\brief Set the cursor appearance.\n    */\n   virtual void setCursor (style::Cursor cursor) = 0;\n\n   /**\n    * \\brief Set the background of the view.\n    */\n   virtual void setBgColor (style::Color *color) = 0;\n\n   /*\n    * ---------------------------------------------------------\n    *    Scrolling and Related. Only usesViewport must be\n    *    implemented, if it returns false, the other methods\n    *    are never called.\n    * -------------------------------------------------------\n    */\n\n   /**\n    * \\brief Return, whether this view uses a viewport.\n    */\n   virtual bool usesViewport () = 0;\n\n   /**\n    * \\brief Get the thickness of the horizontal scrollbar, when it is\n    *    visible.\n    *\n    * Does not have to be implemented, when usesViewport returns false.\n    */\n   virtual int getHScrollbarThickness () = 0;\n\n   /**\n    * \\brief Get the thickness of the vertical scrollbar, when it is\n    *    visible.\n    *\n    * Does not have to be implemented, when usesViewport returns false.\n    */\n   virtual int getVScrollbarThickness () = 0;\n\n   /**\n    * \\brief Scroll the vieport to the given position.\n    *\n    * Does not have to be implemented, when usesViewport returns false.\n    */\n   virtual void scrollTo (int x, int y) = 0;\n\n   /**\n    * \\brief Scroll the viewport as commanded.\n    */\n   virtual void scroll (ScrollCommand) { };\n\n   /**\n    * \\brief Set the viewport size.\n    *\n    * Does not have to be implemented, when usesViewport returns false.\n    *\n    * This will normally imply a resize of the UI widget. Width and height are\n    * the dimensions of the new size, \\em including the scrollbar thicknesses.\n    *\n    */\n   virtual void setViewportSize (int width, int height,\n                                 int hScrollbarThickness,\n                                 int vScrollbarThickness) = 0;\n\n   /*\n    * -----------------------\n    *    Drawing functions\n    * -----------------------\n    */\n\n   /**\n    * \\brief Called before drawing.\n    *\n    * All actual drawing operations will be enclosed into calls of\n    * dw::core:View::startDrawing and dw::core:View::finishDrawing. They\n    * may be implemented, e.g. when a backing\n    * pixmap is used, to prevent flickering. StartDrawing() will then\n    * initialize the backing pixmap, all other drawing operations will draw\n    * into it, and finishDrawing() will merge it into the window.\n    */\n   virtual void startDrawing (Rectangle *area) = 0;\n\n   /**\n    * \\brief Called after drawing.\n    *\n    * \\sa dw::core:View::startDrawing\n    */\n   virtual void finishDrawing (Rectangle *area) = 0;\n\n   /**\n    * \\brief Queue a region, which is given in \\em canvas coordinates, for\n    *    drawing.\n    *\n    * The view implementation is responsible, that this region is drawn, either\n    * immediately, or (which is more typical, since more efficient) the areas\n    * are collected, combined (as far as possible), and the drawing is later\n    * done in an idle function.\n    */\n   virtual void queueDraw (Rectangle *area) = 0;\n\n   /**\n    * \\brief Queue the total viewport for drawing.\n    *\n    * \\sa dw::core::View::queueDraw\n    */\n   virtual void queueDrawTotal () = 0;\n\n   /**\n    * \\brief Cancel a draw queue request.\n    *\n    * If dw::core::View::queueDraw or dw::core::View::queueDrawTotal have been\n    * called before, and the actual drawing was not processed yet, the actual\n    * drawing should be cancelled. Otherwise, the cancellation should be\n    * ignored.\n    */\n   virtual void cancelQueueDraw () = 0;\n\n   /*\n    * The following methods should be self-explaining.\n    */\n\n   virtual void drawPoint     (style::Color *color,\n                               style::Color::Shading shading,\n                               int x, int y) = 0;\n   virtual void drawLine      (style::Color *color,\n                               style::Color::Shading shading,\n                               int x1, int y1, int x2, int y2) = 0;\n   virtual void drawTypedLine (style::Color *color,\n                               style::Color::Shading shading,\n                               style::LineType type, int width,\n                               int x1, int y1, int x2, int y2) = 0;\n   virtual void drawRectangle (style::Color *color,\n                               style::Color::Shading shading, bool filled,\n                               int x, int y, int width, int height) = 0;\n   virtual void drawArc       (style::Color *color,\n                               style::Color::Shading shading, bool filled,\n                               int centerX, int centerY, int width, int height,\n                               int angle1, int angle2) = 0;\n   virtual void drawPolygon    (style::Color *color,\n                                style::Color::Shading shading,\n                                bool filled, bool convex, Point *points,\n                                int npoints) = 0;\n   virtual void drawText       (style::Font *font,\n                                style::Color *color,\n                                style::Color::Shading shading,\n                                int x, int y, const char *text, int len) = 0;\n   virtual void drawSimpleWrappedText (style::Font *font, style::Color *color,\n                                       style::Color::Shading shading,\n                                       int x, int y, int w, int h,\n                                       const char *text) = 0;\n   virtual void drawImage (Imgbuf *imgbuf, int xRoot, int yRoot,\n                           int x, int y, int width, int height) = 0;\n\n   /*\n    * --------------\n    *    Clipping\n    * --------------\n    */\n\n   /*\n    * To prevent drawing outside of a given area, a clipping view may be\n    * requested, which also implements this interface. The clipping view is\n    * related to the parent view (clipping views may be nested!), anything\n    * which is drawn into this clipping view, is later merged again into the\n    * parent view. An implementation will typically use additional pixmaps,\n    * which are later merged into the parent view pixmap/window.\n    */\n\n   virtual View *getClippingView (int x, int y, int width, int height) = 0;\n   virtual void mergeClippingView (View *clippingView) = 0;\n};\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_VIEW_HH__\n"
  },
  {
    "path": "dw/widget.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"core.hh\"\n\n#include \"../lout/msg.h\"\n#include \"../lout/debug.hh\"\n\nusing namespace lout;\nusing namespace lout::object;\nusing namespace lout::misc;\n\nnamespace dw {\nnamespace core {\n\n// ----------------------------------------------------------------------\n\nbool Widget::WidgetImgRenderer::readyToDraw ()\n{\n   return widget->wasAllocated ();\n}\n\nvoid Widget::WidgetImgRenderer::getBgArea (int *x, int *y, int *width,\n                                           int *height)\n{\n   widget->getPaddingArea (x, y, width, height);\n}\n\nvoid Widget::WidgetImgRenderer::getRefArea (int *xRef, int *yRef, int *widthRef,\n                                            int *heightRef)\n{\n   widget->getPaddingArea (xRef, yRef, widthRef, heightRef);\n}\n\nstyle::Style *Widget::WidgetImgRenderer::getStyle ()\n{\n   return widget->getStyle ();\n}\n\nvoid Widget::WidgetImgRenderer::draw (int x, int y, int width, int height)\n{\n   widget->queueDrawArea (x - widget->allocation.x, y - widget->allocation.y,\n                          width, height);\n}\n\n// ----------------------------------------------------------------------\n\nbool Widget::adjustMinWidth = true;\nint Widget::CLASS_ID = -1;\n\nWidget::Widget ()\n{\n   DBG_OBJ_CREATE (\"dw::core::Widget\");\n   registerName (\"dw::core::Widget\", &CLASS_ID);\n\n   DBG_OBJ_ASSOC_CHILD (&requisitionParams);\n   DBG_OBJ_ASSOC_CHILD (&extremesParams);\n\n   flags = (Flags)(NEEDS_RESIZE | EXTREMES_CHANGED);\n   parent = quasiParent = generator = container = NULL;\n   setWidgetReference (NULL);\n   DBG_OBJ_SET_PTR (\"container\", container);\n\n   layout = NULL;\n\n   allocation.x = -1;\n   allocation.y = -1;\n   allocation.width = 1;\n   allocation.ascent = 1;\n   allocation.descent = 0;\n\n   extraSpace.top = extraSpace.right = extraSpace.bottom = extraSpace.left = 0;\n\n   style = NULL;\n   bgColor = NULL;\n   buttonSensitive = true;\n   buttonSensitiveSet = false;\n\n   deleteCallbackData = NULL;\n   deleteCallbackFunc = NULL;\n\n   widgetImgRenderer = NULL;\n\n   stackingContextMgr = NULL;\n}\n\nWidget::~Widget ()\n{\n   if (deleteCallbackFunc)\n      deleteCallbackFunc (deleteCallbackData);\n\n   if (widgetImgRenderer) {\n      if (style && style->backgroundImage)\n         style->backgroundImage->removeExternalImgRenderer (widgetImgRenderer);\n      delete widgetImgRenderer;\n   }\n\n   if (stackingContextMgr)\n      delete stackingContextMgr;\n\n   if (style)\n      style->unref ();\n\n   if (parent)\n      parent->removeChild (this);\n   else if (layout)\n      layout->removeWidget ();\n\n   DBG_OBJ_DELETE ();\n}\n\n\n/**\n * \\brief Calculates the intersection of the visible allocation\n *    (i. e. the intersection with the visible parent allocation) and\n *    \"area\" (in widget coordinates referring to \"refWidget\"),\n *    returned in intersection (in widget coordinates).\n *\n * Typically used by containers when drawing their children (passing\n * \"this\" as \"refWidget\"). Returns whether intersection is not empty.\n */\nbool Widget::intersects (Widget *refWidget, Rectangle *area,\n                         Rectangle *intersection)\n{\n   DBG_OBJ_ENTER (\"draw\", 0, \"intersects\", \"%p, [%d, %d, %d * %d]\",\n                  refWidget, area->x, area->y, area->width, area->height);\n   bool r;\n\n   if (wasAllocated ()) {\n      *intersection = *area;\n      intersection->x += refWidget->allocation.x;\n      intersection->y += refWidget->allocation.y;\n      \n      r = true;\n      // \"RefWidget\" is excluded; it is assumed that \"area\" its already within\n      // its allocation.\n      for (Widget *widget = this; r && widget != refWidget;\n           widget = widget->parent) {\n         assert (widget != NULL); // refWidget must be ancestor.\n\n         Rectangle widgetArea, newIntersection;\n         widgetArea.x = widget->allocation.x;\n         widgetArea.y = widget->allocation.y;\n         widgetArea.width = widget->allocation.width;\n         widgetArea.height = widget->getHeight ();\n\n         if (intersection->intersectsWith (&widgetArea, &newIntersection)) {\n            DBG_OBJ_MSGF (\"draw\", 1, \"new intersection: %d, %d, %d * %d\",\n                          newIntersection.x, newIntersection.y,\n                          newIntersection.width, newIntersection.height);\n            *intersection = newIntersection;\n         } else {\n            DBG_OBJ_MSG (\"draw\", 1, \"no new intersection\");\n            r = false;\n         }\n      }\n\n      if (r) {\n         intersection->x -= allocation.x;\n         intersection->y -= allocation.y;\n\n         DBG_OBJ_MSGF (\"draw\", 1, \"final intersection: %d, %d, %d * %d\",\n                       intersection->x, intersection->y,\n                       intersection->width, intersection->height);\n      }\n   } else {\n      r = false;\n      DBG_OBJ_MSG (\"draw\", 1, \"not allocated\");\n   }\n\n   if (r)\n      DBG_OBJ_LEAVE_VAL (\"true: %d, %d, %d * %d\",\n                         intersection->x, intersection->y,\n                         intersection->width, intersection->height);\n   else\n      DBG_OBJ_LEAVE_VAL0 (\"false\");\n\n   return r;\n}\n\n/**\n * See \\ref dw-interrupted-drawing for details.\n */\nvoid Widget::drawInterruption (View *view, Rectangle *area,\n                               DrawingContext *context)\n{\n   Rectangle thisArea;\n   if (intersects (layout->topLevel, context->getToplevelArea (), &thisArea))\n      draw (view, &thisArea, context);\n\n   context->addWidgetProcessedAsInterruption (this);\n}\n\nWidget *Widget::getWidgetAtPoint (int x, int y,\n                                  GettingWidgetAtPointContext *context)\n{\n   // Suitable for simple widgets, without children.\n\n   if (inAllocation (x, y))\n      return this;\n   else\n      return NULL;\n}\n\nWidget *Widget::getWidgetAtPointInterrupted (int x, int y,\n                                             GettingWidgetAtPointContext\n                                             *context)\n{\n   Widget *widgetAtPoint = getWidgetAtPoint (x, y, context);\n   context->addWidgetProcessedAsInterruption (this);\n   return widgetAtPoint;\n}\n\nvoid Widget::setParent (Widget *parent)\n{\n   DBG_OBJ_ENTER (\"construct\", 0, \"setParent\", \"%p\", parent);\n\n   this->parent = parent;\n   layout = parent->layout;\n\n   if (!buttonSensitiveSet)\n      buttonSensitive = parent->buttonSensitive;\n\n   DBG_OBJ_ASSOC_PARENT (parent);\n   //printf (\"The %s %p becomes a child of the %s %p\\n\",\n   //        getClassName(), this, parent->getClassName(), parent);\n\n   // Determine the container. Currently rather simple; will become\n   // more complicated when absolute and fixed positions are\n   // supported.\n   container = NULL;\n   for (Widget *widget = getParent (); widget != NULL && container == NULL;\n        widget = widget->getParent())\n      if (widget->isPossibleContainer ())\n         container = widget;\n   // If there is no possible container widget, there is\n   // (surprisingly!) also no container (i. e. the viewport is\n   // used). Does not occur in dillo, where the toplevel widget is a\n   // Textblock.\n   DBG_OBJ_SET_PTR (\"container\", container);\n\n   // If at all, stackingContextMgr should have set *before*, see also\n   // Widget::setStyle() and Layout::addWidget().\n   if (stackingContextMgr) {\n      Widget *stackingContextWidget = parent;\n      while (stackingContextWidget &&\n             stackingContextWidget->stackingContextMgr == NULL)\n         stackingContextWidget = stackingContextWidget->parent;\n      assert (stackingContextWidget);\n      stackingContextWidget->stackingContextMgr->addChildSCWidget (this);\n   } else\n      stackingContextWidget = parent->stackingContextWidget;\n\n   notifySetParent();\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Widget::setQuasiParent (Widget *quasiParent)\n{\n   this->quasiParent = quasiParent;\n\n   // More to do? Compare with setParent().\n\n   DBG_OBJ_SET_PTR (\"quasiParent\", quasiParent);\n}\n\nvoid Widget::queueDrawArea (int x, int y, int width, int height)\n{\n   /** \\todo Maybe only the intersection? */\n\n   DBG_OBJ_ENTER (\"draw\", 0, \"queueDrawArea\", \"%d, %d, %d, %d\",\n                  x, y, width, height);\n\n   _MSG(\"Widget::queueDrawArea alloc(%d %d %d %d) wid(%d %d %d %d)\\n\",\n       allocation.x, allocation.y,\n       allocation.width, allocation.ascent + allocation.descent,\n       x, y, width, height);\n   if (layout)\n      layout->queueDraw (x + allocation.x, y + allocation.y, width, height);\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * \\brief This method should be called, when a widget changes its size.\n *\n * A \"fast\" queueResize will ignore the ancestors, and furthermore\n * not trigger the idle function. Used only within\n * viewportSizeChanged, and not available outside Layout and Widget.\n */\nvoid Widget::queueResize (int ref, bool extremesChanged, bool fast)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"queueResize\", \"%d, %s, %s\",\n                  ref, extremesChanged ? \"true\" : \"false\",\n                  fast ? \"true\" : \"false\");\n\n   enterQueueResize ();\n\n   Widget *widget2, *child;\n\n   Flags resizeFlag, extremesFlag, totalFlags;\n\n   if (layout) {\n      // If RESIZE_QUEUED is set, this widget is already in the list.\n      if (!resizeQueued ())\n         layout->queueResizeList->put (this);\n\n      resizeFlag = RESIZE_QUEUED;\n      extremesFlag = EXTREMES_QUEUED;\n   } else {\n      resizeFlag = NEEDS_RESIZE;\n      extremesFlag = EXTREMES_CHANGED;\n   }\n\n   setFlags (resizeFlag);\n   setFlags (ALLOCATE_QUEUED);\n   markSizeChange (ref);\n\n   totalFlags = resizeFlag;\n   \n   if (extremesChanged) {\n      totalFlags = (Flags)(totalFlags | extremesFlag);\n      \n      setFlags (extremesFlag);\n      markExtremesChange (ref);\n   }\n\n   if (fast) {\n      if (parent) {\n         // In this case, queueResize is called from top (may be a\n         // random entry point) to bottom, so markSizeChange and\n         // markExtremesChange have to be called explicitly for the\n         // parent. The tests (needsResize etc.) are uses to check\n         // whether queueResize has been called for the parent, or\n         // whether this widget is the entry point.\n         if (parent->needsResize () || parent->resizeQueued ())\n            parent->markSizeChange (parentRef);\n         if (parent->extremesChanged () || parent->extremesQueued ())\n            parent->markExtremesChange (parentRef);\n      }\n   } else {\n      for (widget2 = parent, child = this; widget2;\n           child = widget2, widget2 = widget2->parent) {         \n         if (layout && !widget2->resizeQueued ())\n            layout->queueResizeList->put (widget2);\n\n         DBG_OBJ_MSGF (\"resize\", 2, \"setting %s and ALLOCATE_QUEUED for %p\",\n                       resizeFlag == RESIZE_QUEUED ?\n                       \"RESIZE_QUEUED\" : \"NEEDS_RESIZE\",\n                       widget2);\n\n         widget2->setFlags (resizeFlag);\n         widget2->markSizeChange (child->parentRef);\n         widget2->setFlags (ALLOCATE_QUEUED);\n\n         if (extremesChanged) {\n            widget2->setFlags (extremesFlag);\n            widget2->markExtremesChange (child->parentRef);\n         }\n\n         DBG_IF_RTFL {\n            if (widget2->parent)\n               DBG_OBJ_MSGF (\"resize\", 2,\n                             \"checking parent %p: (%d & %d) [= %d] == %d?\",\n                             widget2->parent, widget2->parent->flags,\n                             totalFlags, widget2->parent->flags & totalFlags,\n                             totalFlags);\n         }\n         \n         if (widget2->parent &&\n             (widget2->parent->flags & totalFlags) == totalFlags) {\n            widget2->parent->markSizeChange (widget2->parentRef);\n            if (extremesChanged) {\n               widget2->parent->markExtremesChange (widget2->parentRef);\n            }\n            \n            break;\n         }\n      }\n\n      if (layout)\n         layout->queueResize (extremesChanged);\n   }\n\n   leaveQueueResize ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Widget::containerSizeChanged ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChanged\");\n\n   // If there is a container widget (not the viewport), which has not\n   // changed its size (which can be determined by the respective\n   // flags: this method is called recursively), this widget will\n   // neither change its size. Also, the recursive iteration can be\n   // stopped, since the children of this widget will\n   if (container == NULL ||\n       container->needsResize () || container->resizeQueued () ||\n       container->extremesChanged () || container->extremesQueued ()) {\n      // Viewport (container == NULL) or container widget has changed\n      // its size.\n      if (affectedByContainerSizeChange ())\n         queueResizeFast (0, true);\n\n      // Even if *this* widget is not affected, children may be, so\n      // iterate over children.\n      containerSizeChangedForChildren ();\n   }\n\n   DBG_OBJ_LEAVE ();\n}\n\nbool Widget::affectedByContainerSizeChange ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"affectedByContainerSizeChange\");\n\n   bool ret;\n\n   // This standard implementation is suitable for all widgets which\n   // call correctRequisition() and correctExtremes(), even in the way\n   // how Textblock and Image do (see comments there). Has to be kept\n   // in sync.\n\n   if (container == NULL) {\n      if (style::isAbsLength (getStyle()->width) &&\n          style::isAbsLength (getStyle()->height))\n         // Both absolute, i. e. fixed: no dependency.\n         ret = false;\n      else if (style::isPerLength (getStyle()->width) ||\n               style::isPerLength (getStyle()->height)) {\n         // Any percentage: certainly dependenant.\n         ret = true;\n      } else\n         // One or both is \"auto\": depends ...\n         ret =\n            (getStyle()->width == style::LENGTH_AUTO ?\n             usesAvailWidth () : false) ||\n            (getStyle()->height == style::LENGTH_AUTO ?\n             usesAvailHeight () : false);\n   } else\n      ret = container->affectsSizeChangeContainerChild (this);\n\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr(ret));\n   return ret;\n}\n\nbool Widget::affectsSizeChangeContainerChild (Widget *child)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"affectsSizeChangeContainerChild\", \"%p\", child);\n\n   bool ret;\n\n   // From the point of view of the container. This standard\n   // implementation should be suitable for most (if not all)\n   // containers.\n\n   if (style::isAbsLength (child->getStyle()->width) &&\n       style::isAbsLength (child->getStyle()->height))\n      // Both absolute, i. e. fixed: no dependency.\n      ret = false;\n   else if (style::isPerLength (child->getStyle()->width) ||\n            style::isPerLength (child->getStyle()->height)) {\n      // Any percentage: certainly dependenant.\n      ret = true;\n   } else\n      // One or both is \"auto\": depends ...\n      ret =\n         (child->getStyle()->width == style::LENGTH_AUTO ?\n          child->usesAvailWidth () : false) ||\n         (child->getStyle()->height == style::LENGTH_AUTO ?\n          child->usesAvailHeight () : false);\n\n   DBG_OBJ_LEAVE_VAL (\"%s\", boolToStr(ret));\n   return ret;\n}\n\nvoid Widget::containerSizeChangedForChildren ()\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"containerSizeChangedForChildren\");\n\n   // Working, but inefficient standard implementation.\n   Iterator *it = iterator ((Content::Type)(Content::WIDGET_IN_FLOW |\n                                            Content::WIDGET_OOF_CONT),\n                            false);\n   while (it->next ())\n      it->getContent()->widget->containerSizeChanged ();\n   it->unref ();\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * \\brief Must be implemengted by a method returning true, when\n *    getAvailWidth() is called.\n */\nbool Widget::usesAvailWidth ()\n{\n   return false;\n}\n\n/**\n * \\brief Must be implemengted by a method returning true, when\n *    getAvailHeight() is called.\n */\nbool Widget::usesAvailHeight ()\n{\n   return false;\n}\n\n/**\n *  \\brief This method is a wrapper for Widget::sizeRequestImpl(); it calls\n *     the latter only when needed.\n */\nvoid Widget::sizeRequest (Requisition *requisition, int numPos,\n                          Widget **references, int *x, int *y)\n{\n   assert (!queueResizeEntered ());\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"sizeRequest\", \"%d, ...\", numPos);\n\n   DBG_IF_RTFL {\n      DBG_OBJ_MSG_START();\n      for(int i = 0; i < numPos; i++)\n         DBG_OBJ_MSGF (\"resize\", 1, \"ref #%d: %p, %d, %d\",\n                       i, references[i], x[i], y[i]);\n      DBG_OBJ_MSG_END();\n   }\n\n   enterSizeRequest ();\n\n   if (resizeQueued ()) {\n      // This method is called outside of Layout::resizeIdle.\n      setFlags (NEEDS_RESIZE);\n      unsetFlags (RESIZE_QUEUED);\n      // The widget is not taken out of Layout::queueResizeList, since\n      // other *_QUEUED flags may still be set and processed in\n      // Layout::resizeIdle.\n   }\n\n   SizeParams newRequisitionParams (numPos, references, x, y);\n   DBG_OBJ_ASSOC_CHILD (&newRequisitionParams);\n\n   bool callImpl;\n   if (needsResize ())\n      callImpl = true;\n   else {\n      // Even if RESIZE_QUEUED / NEEDS_RESIZE is not set, calling\n      // sizeRequestImpl is necessary when the relative positions passed here\n      // have changed.\n      callImpl = !newRequisitionParams.isEquivalent (&requisitionParams);\n   }\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"callImpl = %s\", boolToStr (callImpl));\n\n   requisitionParams = newRequisitionParams;\n\n   if (callImpl) {\n      calcExtraSpace (numPos, references, x, y);\n      /** \\todo Check requisition == &(this->requisition) and do what? */\n      sizeRequestImpl (requisition, numPos, references, x, y);\n      this->requisition = *requisition;\n      unsetFlags (NEEDS_RESIZE);\n\n      DBG_OBJ_SET_NUM (\"requisition.width\", requisition->width);\n      DBG_OBJ_SET_NUM (\"requisition.ascent\", requisition->ascent);\n      DBG_OBJ_SET_NUM (\"requisition.descent\", requisition->descent);\n   } else\n      *requisition = this->requisition;\n\n   leaveSizeRequest ();\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * \\brief Used to evaluate Widget::adjustMinWidth.\n *\n * If extremes == NULL, getExtremes is called. ForceValue is the same\n * value passed to getAvailWidth etc.; if false, getExtremes is not\n * called. A value of \"false\" is passed for \"useCorrected\" in the\n * context of correctExtemes etc., to avoid cyclic dependencies.\n * \n */\nint Widget::getMinWidth (Extremes *extremes, bool forceValue)\n{\n   DBG_IF_RTFL {\n      if (extremes)\n         DBG_OBJ_ENTER (\"resize\", 0, \"getMinWidth\", \"[%d (%d) / %d (%d)], %s\",\n                        extremes->minWidth, extremes->minWidthIntrinsic,\n                        extremes->maxWidth, extremes->maxWidthIntrinsic,\n                        forceValue ? \"true\" : \"false\");\n      else\n         DBG_OBJ_ENTER (\"resize\", 0, \"getMinWidth\", \"(nil), %s\",\n                        forceValue ? \"true\" : \"false\");\n   }\n\n   int minWidth;\n\n   if (getAdjustMinWidth ()) {\n      Extremes extremes2;\n      if (extremes == NULL) {\n         if (forceValue) {\n            getExtremes (&extremes2);\n            extremes = &extremes2;\n         }\n      }\n\n      // TODO Not completely clear whether this is feasable: Within\n      // the context of getAvailWidth(false) etc., getExtremes may not\n      // be called. We ignore the minimal width then.\n      if (extremes)\n         minWidth = extremes->adjustmentWidth;\n      else\n         minWidth = 0;\n   } else\n      minWidth = 0;\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", minWidth);\n   return minWidth;\n}\n\n/**\n * Return available width including margin/border/padding\n * (extraSpace?), not only the content width.\n */\nint Widget::getAvailWidth (bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"getAvailWidth\", \"%s\",\n                  forceValue ? \"true\" : \"false\");\n\n   int width;\n\n   if (parent == NULL && quasiParent == NULL) {\n      DBG_OBJ_MSG (\"resize\", 1, \"no parent, regarding viewport\");\n      DBG_OBJ_MSG_START ();\n\n      // TODO Consider nested layouts (e. g. <button>).\n\n      int viewportWidth =\n         layout->viewportWidth - (layout->canvasHeightGreater ?\n                                  layout->vScrollbarThickness : 0);\n      width = -1;\n      calcFinalWidth (getStyle (), viewportWidth, NULL, 0, forceValue, &width);\n      if (width == -1)\n         width = viewportWidth;\n\n      DBG_OBJ_MSG_END ();\n   } else if (parent) {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to parent\");\n      DBG_OBJ_MSG_START ();\n      width = parent->getAvailWidthOfChild (this, forceValue);\n      DBG_OBJ_MSG_END ();\n   } else /* if (quasiParent) */ {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to quasiParent\");\n      DBG_OBJ_MSG_START ();\n      width = quasiParent->getAvailWidthOfChild (this, forceValue);\n      DBG_OBJ_MSG_END ();\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", width);\n   return width;\n}\n\n/**\n * Return available height including margin/border/padding\n * (extraSpace?), not only the content height.\n */\nint Widget::getAvailHeight (bool forceValue)\n{\n   // TODO Correct by ... not extremes, but ...? (Height extremes?)\n\n   // TODO Consider 'min-height' and 'max-height'. (Minor priority, as long as\n   //      \"getAvailHeight (true)\" is not used.\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"getAvailHeight\", \"%s\",\n                  forceValue ? \"true\" : \"false\");\n\n   int height;\n\n   if (parent == NULL && quasiParent == NULL) {\n      DBG_OBJ_MSG (\"resize\", 1, \"no parent, regarding viewport\");\n      DBG_OBJ_MSG_START ();\n\n      // TODO Consider nested layouts (e. g. <button>).\n      if (style::isAbsLength (getStyle()->height)) {\n         DBG_OBJ_MSGF (\"resize\", 1, \"absolute height: %dpx\",\n                       style::absLengthVal (getStyle()->height));\n         height = style::absLengthVal (getStyle()->height) + boxDiffHeight ();\n      } else if (style::isPerLength (getStyle()->height)) {\n         DBG_OBJ_MSGF (\"resize\", 1, \"percentage height: %g%%\",\n                       100 * style::perLengthVal_useThisOnlyForDebugging\n                                (getStyle()->height));\n         // Notice that here -- unlike getAvailWidth() --\n         // layout->hScrollbarThickness is not considered here;\n         // something like canvasWidthGreater (analogue to\n         // canvasHeightGreater) would be complicated and lead to\n         // possibly contradictory self-references.\n         height = applyPerHeight (layout->viewportHeight, getStyle()->height);\n      } else {\n         DBG_OBJ_MSG (\"resize\", 1, \"no specification\");\n         height = layout->viewportHeight;\n      }\n\n      DBG_OBJ_MSG_END ();\n   } else if (parent) {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to parent\");\n      DBG_OBJ_MSG_START ();\n      height = parent->getAvailHeightOfChild (this, forceValue);\n      DBG_OBJ_MSG_END ();\n   } else /* if (quasiParent) */ {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to quasiParent\");\n      DBG_OBJ_MSG_START ();\n      height = quasiParent->getAvailHeightOfChild (this, forceValue);\n      DBG_OBJ_MSG_END ();\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", height);\n   return height;\n}\n\nvoid Widget::correctRequisition (Requisition *requisition,\n                                 void (*splitHeightFun) (int, int *, int *),\n                                 bool allowDecreaseWidth,\n                                 bool allowDecreaseHeight)\n{\n   // TODO Correct height by ... not extremes, but ...? (Height extremes?)\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"correctRequisition\",\n                  \"%d * (%d + %d), ..., %s, %s\",\n                  requisition->width, requisition->ascent,\n                  requisition->descent, misc::boolToStr (allowDecreaseWidth),\n                  misc::boolToStr (allowDecreaseHeight));\n\n   if (parent == NULL && quasiParent == NULL) {\n      DBG_OBJ_MSG (\"resize\", 1, \"no parent, regarding viewport\");\n      DBG_OBJ_MSG_START ();\n\n      int limitMinWidth = getMinWidth (NULL, true);\n      if (!allowDecreaseWidth && limitMinWidth < requisition->width)\n         limitMinWidth = requisition->width;\n      \n      int viewportWidth =\n         layout->viewportWidth - (layout->canvasHeightGreater ?\n                                  layout->vScrollbarThickness : 0);\n      calcFinalWidth (getStyle (), viewportWidth, NULL, limitMinWidth, false,\n                      &requisition->width);\n\n      // For layout->viewportHeight, see comment in getAvailHeight().\n      int height = calcHeight (getStyle()->height, false,\n                               layout->viewportHeight, NULL, false);\n      adjustHeight (&height, allowDecreaseHeight, requisition->ascent,\n                    requisition->descent);\n\n      int minHeight = calcHeight (getStyle()->minHeight, false,\n                                  layout->viewportHeight, NULL, false);\n      adjustHeight (&minHeight, allowDecreaseHeight, requisition->ascent,\n                    requisition->descent);\n         \n      int maxHeight = calcHeight (getStyle()->maxHeight, false,\n                                  layout->viewportHeight, NULL, false);\n      adjustHeight (&maxHeight, allowDecreaseHeight, requisition->ascent,\n                    requisition->descent);\n\n      // TODO Perhaps split first, then add box ascent and descent.\n      if (height != -1)\n         splitHeightFun (height, &requisition->ascent, &requisition->descent);\n      if (minHeight != -1 &&\n          requisition->ascent + requisition->descent < minHeight)\n         splitHeightFun (minHeight, &requisition->ascent,\n                         &requisition->descent);\n      if (maxHeight != -1 &&\n          requisition->ascent + requisition->descent > maxHeight)\n         splitHeightFun (maxHeight, &requisition->ascent,\n                         &requisition->descent);\n\n      DBG_OBJ_MSG_END ();\n   } else if (parent) {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to parent\");\n      DBG_OBJ_MSG_START ();\n      parent->correctRequisitionOfChild (this, requisition, splitHeightFun,\n                                         allowDecreaseWidth,\n                                         allowDecreaseHeight);\n      DBG_OBJ_MSG_END ();\n   } else /* if (quasiParent) */ {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to quasiParent\");\n      DBG_OBJ_MSG_START ();\n      quasiParent->correctRequisitionOfChild (this, requisition,\n                                              splitHeightFun,\n                                              allowDecreaseWidth,\n                                              allowDecreaseHeight);\n      DBG_OBJ_MSG_END ();\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d * (%d + %d)\", requisition->width, requisition->ascent,\n                      requisition->descent);\n}\n\nvoid Widget::correctExtremes (Extremes *extremes, bool useAdjustmentWidth)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"correctExtremes\", \"%d (%d) / %d (%d)\",\n                  extremes->minWidth, extremes->minWidthIntrinsic,\n                  extremes->maxWidth, extremes->maxWidthIntrinsic);\n\n   if (container == NULL && quasiParent == NULL) {\n      DBG_OBJ_MSG (\"resize\", 1, \"no parent, regarding viewport\");\n      DBG_OBJ_MSG_START ();\n\n      int limitMinWidth =\n         useAdjustmentWidth ? getMinWidth (extremes, false) : 0;\n      int viewportWidth =\n         layout->viewportWidth - (layout->canvasHeightGreater ?\n                                  layout->vScrollbarThickness : 0);\n\n      int width = calcWidth (getStyle()->width, viewportWidth, NULL,\n                             limitMinWidth, false);\n      int minWidth = calcWidth (getStyle()->minWidth, viewportWidth, NULL,\n                                limitMinWidth, false);\n      int maxWidth = calcWidth (getStyle()->maxWidth, viewportWidth, NULL,\n                                limitMinWidth, false);\n\n      DBG_OBJ_MSGF (\"resize\", 1, \"width = %d, minWidth = %d, maxWidth = %d\",\n                    width, minWidth, maxWidth);\n\n      if (width != -1)\n         extremes->minWidth = extremes->maxWidth = width;\n      if (minWidth != -1)\n         extremes->minWidth = minWidth;\n      if (maxWidth != -1)\n         extremes->maxWidth = maxWidth;\n\n      DBG_OBJ_MSG_END ();\n   } else if (parent) {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to parent\");\n      DBG_OBJ_MSG_START ();\n      parent->correctExtremesOfChild (this, extremes, useAdjustmentWidth);\n      DBG_OBJ_MSG_END ();\n   } else /* if (quasiParent) */ {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to quasiParent\");\n      DBG_OBJ_MSG_START ();\n      quasiParent->correctExtremesOfChild (this, extremes, useAdjustmentWidth);\n      DBG_OBJ_MSG_END ();\n   }\n\n   if (extremes->maxWidth < extremes->minWidth)\n      extremes->maxWidth = extremes->minWidth;\n\n   DBG_OBJ_LEAVE_VAL (\"%d / %d\", extremes->minWidth, extremes->maxWidth);\n}\n\nint Widget::calcWidth (style::Length cssValue, int refWidth, Widget *refWidget,\n                       int limitMinWidth, bool forceValue)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcWidth\", \"0x%x, %d, %p, %d\",\n                  cssValue, refWidth, refWidget, limitMinWidth);\n\n   assert (refWidth != -1 || refWidget != NULL);\n\n   int width;\n\n   if (style::isAbsLength (cssValue)) {\n      DBG_OBJ_MSGF (\"resize\", 1, \"absolute width: %dpx\",\n                    style::absLengthVal (cssValue));\n      width = misc::max (style::absLengthVal (cssValue) + boxDiffWidth (),\n                         limitMinWidth);\n   } else if (style::isPerLength (cssValue)) {\n      DBG_OBJ_MSGF (\"resize\", 1, \"percentage width: %g%%\",\n                    100 * style::perLengthVal_useThisOnlyForDebugging\n                             (cssValue));\n      if (refWidth != -1)\n         width = misc::max (applyPerWidth (refWidth, cssValue), limitMinWidth);\n      else {\n         int availWidth = refWidget->getAvailWidth (forceValue);\n         if (availWidth != -1) {\n            int containerWidth = availWidth - refWidget->boxDiffWidth ();\n            width = misc::max (applyPerWidth (containerWidth, cssValue),\n                               limitMinWidth);\n         } else\n            width = -1;\n      }\n   } else {\n      DBG_OBJ_MSG (\"resize\", 1, \"not specified\");\n      width = -1;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", width);\n   return width;\n}\n\n// *finalWidth may be -1.\nvoid Widget::calcFinalWidth (style::Style *style, int refWidth,\n                             Widget *refWidget, int limitMinWidth,\n                             bool forceValue, int *finalWidth)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcFinalWidth\", \"..., %d, %p, %d, [%d]\",\n                  refWidth, refWidget, limitMinWidth, *finalWidth);\n\n   int width = calcWidth (style->width, refWidth, refWidget, limitMinWidth,\n                          forceValue);\n   int minWidth = calcWidth (style->minWidth, refWidth, refWidget,\n                             limitMinWidth, forceValue);\n   int maxWidth = calcWidth (style->maxWidth, refWidth, refWidget,\n                             limitMinWidth, forceValue);\n\n   DBG_OBJ_MSGF (\"resize\", 1, \"width = %d, minWidth = %d, maxWidth = %d\",\n                 width, minWidth, maxWidth);\n   \n   if (width != -1)\n      *finalWidth = width;\n   if (minWidth != -1 && *finalWidth != -1 && *finalWidth < minWidth)\n      *finalWidth = minWidth;\n   if (maxWidth != -1 && *finalWidth == -1 && *finalWidth > maxWidth)\n      *finalWidth = maxWidth;\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", *finalWidth);\n}\n\nint Widget::calcHeight (style::Length cssValue, bool usePercentage,\n                        int refHeight, Widget *refWidget, bool forceValue)\n{\n   // TODO Search for usage of this method and check the value of\n   // \"usePercentage\"; this has to be clarified.\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"calcHeight\", \"0x%x, %s, %d, %p\",\n                  cssValue, usePercentage ? \"true\" : \"false\", refHeight,\n                  refWidget);\n\n   assert (refHeight != -1 || refWidget != NULL);\n\n   int height;\n\n   if (style::isAbsLength (cssValue)) {\n      DBG_OBJ_MSGF (\"resize\", 1, \"absolute height: %dpx\",\n                    style::absLengthVal (cssValue));\n      height =\n         misc::max (style::absLengthVal (cssValue) + boxDiffHeight (), 0);\n   } else if (style::isPerLength (cssValue)) {\n      DBG_OBJ_MSGF (\"resize\", 1, \"percentage height: %g%%\",\n                    100 *\n                    style::perLengthVal_useThisOnlyForDebugging (cssValue));\n      if (usePercentage) {\n         if (refHeight != -1)\n            height = misc::max (applyPerHeight (refHeight, cssValue), 0);\n         else {\n            int availHeight = refWidget->getAvailHeight (forceValue);\n            if (availHeight != -1) {\n               int containerHeight = availHeight - refWidget->boxDiffHeight ();\n               height =\n                  misc::max (applyPerHeight (containerHeight, cssValue), 0);\n            } else\n               height = -1;\n         }\n      } else\n         height = -1;\n   } else {\n      DBG_OBJ_MSG (\"resize\", 1, \"not specified\");\n      height = -1;\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", height);\n   return height;\n}\n\nvoid Widget::adjustHeight (int *height, bool allowDecreaseHeight, int ascent,\n                           int descent)\n{\n   if (!allowDecreaseHeight && *height != -1 && *height < ascent + descent)\n      *height = ascent + descent;\n}\n\n/**\n * \\brief Wrapper for Widget::getExtremesImpl().\n */\nvoid Widget::getExtremes (Extremes *extremes, int numPos, Widget **references,\n                          int *x, int *y)\n{\n   assert (!queueResizeEntered ());\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"getExtremes\", \"%d, ...\", numPos);\n\n   enterGetExtremes ();\n\n   if (extremesQueued ()) {\n      // This method is called outside of Layout::resizeIdle.\n      setFlags (EXTREMES_CHANGED);\n      unsetFlags (EXTREMES_QUEUED);\n      // The widget is not taken out of Layout::queueResizeList, since\n      // other *_QUEUED flags may still be set and processed in\n      // Layout::resizeIdle.\n   }\n\n   bool callImpl;\n   if (extremesChanged ())\n      callImpl = true;\n   else {\n      // Even if EXTREMES_QUEUED / EXTREMES_CHANGED is not set, calling\n      // getExtremesImpl is necessary when the relavive positions passed here\n      // have changed.\n      SizeParams newParams (numPos, references, x, y);\n      DBG_OBJ_ASSOC_CHILD (&newParams);\n      if (newParams.isEquivalent (&extremesParams))\n         callImpl = false;\n      else {\n         callImpl = true;\n         extremesParams = newParams;\n      }\n   }\n   \n   if (callImpl) {\n      // For backward compatibility (part 1/2):\n      extremes->minWidthIntrinsic = extremes->maxWidthIntrinsic = -1;\n\n      getExtremesImpl (extremes, numPos, references, x, y);\n\n      // For backward compatibility (part 2/2):\n      if (extremes->minWidthIntrinsic == -1)\n         extremes->minWidthIntrinsic = extremes->minWidth;\n      if (extremes->maxWidthIntrinsic == -1)\n         extremes->maxWidthIntrinsic = extremes->maxWidth;\n\n      this->extremes = *extremes;\n      unsetFlags (EXTREMES_CHANGED);\n\n      DBG_OBJ_SET_NUM (\"extremes.minWidth\", extremes->minWidth);\n      DBG_OBJ_SET_NUM (\"extremes.minWidthIntrinsic\",\n                       extremes->minWidthIntrinsic);\n      DBG_OBJ_SET_NUM (\"extremes.maxWidth\", extremes->maxWidth);\n      DBG_OBJ_SET_NUM (\"extremes.maxWidthIntrinsic\",\n                       extremes->maxWidthIntrinsic);\n      DBG_OBJ_SET_NUM (\"extremes.adjustmentWidth\", extremes->adjustmentWidth);\n   } else\n      *extremes = this->extremes;\n\n   leaveGetExtremes ();\n\n   DBG_OBJ_LEAVE ();\n}\n\n/**\n * \\brief Calculates dw::core::Widget::extraSpace.\n *\n * Delegated to dw::core::Widget::calcExtraSpaceImpl. Called both from\n * dw::core::Widget::sizeRequest and dw::core::Widget::getExtremes.\n */\nvoid Widget::calcExtraSpace (int numPos, Widget **references, int *x, int *y)\n{\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"calcExtraSpace\");\n\n   extraSpace.top = extraSpace.right = extraSpace.bottom = extraSpace.left = 0;\n   calcExtraSpaceImpl (numPos, references, x, y);\n\n   DBG_OBJ_SET_NUM (\"extraSpace.top\", extraSpace.top);\n   DBG_OBJ_SET_NUM (\"extraSpace.bottom\", extraSpace.bottom);\n   DBG_OBJ_SET_NUM (\"extraSpace.left\", extraSpace.left);\n   DBG_OBJ_SET_NUM (\"extraSpace.right\", extraSpace.right);\n\n   DBG_OBJ_LEAVE ();\n}\n\nint Widget::numSizeRequestReferences ()\n{\n   return 0;\n}\n\nWidget *Widget::sizeRequestReference (int index)\n{\n   misc::notImplemented (\"Widget::sizeRequestReference\");\n   return NULL;\n}\n\nint Widget::numGetExtremesReferences ()\n{\n   return 0;\n}\n\nWidget *Widget::getExtremesReference (int index)\n{\n   misc::notImplemented (\"Widget::getExtremesReference\");\n   return NULL;\n}\n\n/**\n * \\brief Wrapper for Widget::sizeAllocateImpl, calls the latter only when\n *    needed.\n */\nvoid Widget::sizeAllocate (Allocation *allocation)\n{\n   assert (!queueResizeEntered ());\n   assert (!sizeRequestEntered ());\n   assert (!getExtremesEntered ());\n   assert (resizeIdleEntered ());\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"sizeAllocate\", \"%d, %d; %d * (%d + %d)\",\n                  allocation->x, allocation->y, allocation->width,\n                  allocation->ascent, allocation->descent);\n\n   DBG_OBJ_MSGF (\"resize\", 1,\n                 \"old allocation (%d, %d; %d * (%d + %d)); needsAllocate: %s\",\n                 this->allocation.x, this->allocation.y, this->allocation.width,\n                 this->allocation.ascent, this->allocation.descent,\n                 needsAllocate () ? \"true\" : \"false\");\n\n   enterSizeAllocate ();\n\n   /*printf (\"The %stop-level %s %p is allocated:\\n\",\n           parent ? \"non-\" : \"\", getClassName(), this);\n   printf (\"   old = (%d, %d, %d + (%d + %d))\\n\",\n           this->allocation.x, this->allocation.y, this->allocation.width,\n           this->allocation.ascent, this->allocation.descent);\n   printf (\"   new = (%d, %d, %d + (%d + %d))\\n\",\n           allocation->x, allocation->y, allocation->width, allocation->ascent,\n           allocation->descent);\n   printf (\"   NEEDS_ALLOCATE = %s\\n\", needsAllocate () ? \"true\" : \"false\");*/\n\n   if (needsAllocate () ||\n       allocation->x != this->allocation.x ||\n       allocation->y != this->allocation.y ||\n       allocation->width != this->allocation.width ||\n       allocation->ascent != this->allocation.ascent ||\n       allocation->descent != this->allocation.descent) {\n\n      if (wasAllocated ()) {\n         layout->queueDrawExcept (\n            this->allocation.x,\n            this->allocation.y,\n            this->allocation.width,\n            this->allocation.ascent + this->allocation.descent,\n            allocation->x,\n            allocation->y,\n            allocation->width,\n            allocation->ascent + allocation->descent);\n      }\n\n      sizeAllocateImpl (allocation);\n\n      //DEBUG_MSG (DEBUG_ALLOC, \"... to %d, %d, %d x %d x %d\\n\",\n      //           widget->allocation.x, widget->allocation.y,\n      //           widget->allocation.width, widget->allocation.ascent,\n      //           widget->allocation.descent);\n\n      this->allocation = *allocation;\n      unsetFlags (NEEDS_ALLOCATE);\n      setFlags (WAS_ALLOCATED);\n\n      resizeDrawImpl ();\n\n      DBG_OBJ_SET_NUM (\"allocation.x\", this->allocation.x);\n      DBG_OBJ_SET_NUM (\"allocation.y\", this->allocation.y);\n      DBG_OBJ_SET_NUM (\"allocation.width\", this->allocation.width);\n      DBG_OBJ_SET_NUM (\"allocation.ascent\", this->allocation.ascent);\n      DBG_OBJ_SET_NUM (\"allocation.descent\", this->allocation.descent);\n   }\n\n   /*unsetFlags (NEEDS_RESIZE);*/\n\n   leaveSizeAllocate ();\n\n   DBG_OBJ_LEAVE ();\n}\n\nbool Widget::buttonPress (EventButton *event)\n{\n   return buttonPressImpl (event);\n}\n\nbool Widget::buttonRelease (EventButton *event)\n{\n   return buttonReleaseImpl (event);\n}\n\nbool Widget::motionNotify (EventMotion *event)\n{\n   return motionNotifyImpl (event);\n}\n\nvoid Widget::enterNotify (EventCrossing *event)\n{\n   enterNotifyImpl (event);\n}\n\nvoid Widget::leaveNotify (EventCrossing *event)\n{\n   leaveNotifyImpl (event);\n}\n\n/**\n *  \\brief Change the style of a widget.\n *\n * The old style is automatically unreferred, the new is referred. If this\n * call causes the widget to change its size, dw::core::Widget::queueResize\n * is called.\n */\nvoid Widget::setStyle (style::Style *style)\n{\n   bool sizeChanged;\n\n   if (widgetImgRenderer && this->style && this->style->backgroundImage)\n      this->style->backgroundImage->removeExternalImgRenderer\n         (widgetImgRenderer);\n\n   style->ref ();\n\n   if (this->style) {\n      sizeChanged = this->style->sizeDiffs (style);\n      this->style->unref ();\n   } else\n      sizeChanged = true;\n\n   this->style = style;\n\n   DBG_OBJ_ASSOC_CHILD (style);\n\n   if (style && style->backgroundImage) {\n      // Create instance of WidgetImgRenderer when needed. Until this\n      // widget is deleted, \"widgetImgRenderer\" will be kept, since it\n      // is not specific to the style, but only to this widget.\n      if (widgetImgRenderer == NULL)\n         widgetImgRenderer = new WidgetImgRenderer (this);\n      style->backgroundImage->putExternalImgRenderer (widgetImgRenderer);\n   }\n\n   if (layout != NULL) {\n      layout->updateCursor ();\n   }\n\n   // After Layout::addWidget() (as toplevel widget) or Widget::setParent()\n   // (which also sets layout), changes of the style cannot be considered\n   // anymore. (Should print a warning?)\n   if (layout == NULL &&\n       StackingContextMgr::isEstablishingStackingContext (this)) {\n      stackingContextMgr = new StackingContextMgr (this);\n      DBG_OBJ_ASSOC_CHILD (stackingContextMgr);\n      stackingContextWidget = this;\n   }\n\n   if (sizeChanged)\n      queueResize (0, true);\n   else\n      queueDraw ();\n\n   // These should better be attributed to the style itself, and a\n   // script processing RTFL messages could transfer it to something\n   // equivalent:\n\n   DBG_OBJ_SET_NUM (\"style.margin.top\", style->margin.top);\n   DBG_OBJ_SET_NUM (\"style.margin.bottom\", style->margin.bottom);\n   DBG_OBJ_SET_NUM (\"style.margin.left\", style->margin.left);\n   DBG_OBJ_SET_NUM (\"style.margin.right\", style->margin.right);\n\n   DBG_OBJ_SET_NUM (\"style.border-width.top\", style->borderWidth.top);\n   DBG_OBJ_SET_NUM (\"style.border-width.bottom\", style->borderWidth.bottom);\n   DBG_OBJ_SET_NUM (\"style.border-width.left\", style->borderWidth.left);\n   DBG_OBJ_SET_NUM (\"style.border-width.right\", style->borderWidth.right);\n\n   DBG_OBJ_SET_NUM (\"style.padding.top\", style->padding.top);\n   DBG_OBJ_SET_NUM (\"style.padding.bottom\", style->padding.bottom);\n   DBG_OBJ_SET_NUM (\"style.padding.left\", style->padding.left);\n   DBG_OBJ_SET_NUM (\"style.padding.right\", style->padding.right);\n\n   DBG_OBJ_SET_NUM (\"style.border-spacing (h)\", style->hBorderSpacing);\n   DBG_OBJ_SET_NUM (\"style.border-spacing (v)\", style->vBorderSpacing);\n\n   DBG_OBJ_SET_SYM (\"style.display\",\n                    style->display == style::DISPLAY_BLOCK ? \"block\" :\n                    style->display == style::DISPLAY_INLINE ? \"inline\" :\n                    style->display == style::DISPLAY_INLINE_BLOCK ?\n                       \"inline-block\" :\n                    style->display == style::DISPLAY_LIST_ITEM ? \"list-item\" :\n                    style->display == style::DISPLAY_NONE ? \"none\" :\n                    style->display == style::DISPLAY_TABLE ? \"table\" :\n                    style->display == style::DISPLAY_TABLE_ROW_GROUP ?\n                       \"table-row-group\" :\n                    style->display == style::DISPLAY_TABLE_HEADER_GROUP ?\n                       \"table-header-group\" :\n                    style->display == style::DISPLAY_TABLE_FOOTER_GROUP ?\n                       \"table-footer-group\" :\n                    style->display == style::DISPLAY_TABLE_ROW ? \"table-row\" :\n                    style->display == style::DISPLAY_TABLE_CELL ? \"table-cell\" :\n                    \"???\");\n\n   DBG_OBJ_SET_NUM (\"style.width (raw)\", style->width);\n   DBG_OBJ_SET_NUM (\"style.min-width (raw)\", style->minWidth);\n   DBG_OBJ_SET_NUM (\"style.max-width (raw)\", style->maxWidth);\n   DBG_OBJ_SET_NUM (\"style.height (raw)\", style->height);\n   DBG_OBJ_SET_NUM (\"style.min-height (raw)\", style->minHeight);\n   DBG_OBJ_SET_NUM (\"style.max-height (raw)\", style->maxHeight);\n\n   if (style->backgroundColor)\n      DBG_OBJ_SET_COL (\"style.background-color\",\n                       style->backgroundColor->getColor ());\n   else\n      DBG_OBJ_SET_SYM (\"style.background-color\", \"transparent\");\n}\n\n/**\n * \\brief Set the background \"behind\" the widget, if it is not the\n *    background of the parent widget, e.g. the background of a table\n *    row.\n */\nvoid Widget::setBgColor (style::Color *bgColor)\n{\n   this->bgColor = bgColor;\n}\n\n/**\n * \\brief Get the actual background of a widget.\n */\nstyle::Color *Widget::getBgColor ()\n{\n   Widget *widget = this;\n\n   while (widget != NULL) {\n      if (widget->style->backgroundColor)\n         return widget->style->backgroundColor;\n      if (widget->bgColor)\n         return widget->bgColor;\n\n      widget = widget->parent;\n   }\n\n   return layout->getBgColor ();\n}\n\n\n/**\n * \\brief Draw borders and background of a widget part, which allocation is\n *    given by (x, y, width, height) (widget coordinates).\n *\n * area is given in widget coordinates.\n */\nvoid Widget::drawBox (View *view, style::Style *style, Rectangle *area,\n                      int x, int y, int width, int height, bool inverse)\n{\n   Rectangle canvasArea;\n   canvasArea.x = area->x + allocation.x;\n   canvasArea.y = area->y + allocation.y;\n   canvasArea.width = area->width;\n   canvasArea.height = area->height;\n\n   style::drawBorder (view, layout, &canvasArea,\n                      allocation.x + x, allocation.y + y,\n                      width, height, style, inverse);\n\n   // This method is used for inline elements, where the CSS 2 specification\n   // does not define what here is called \"reference area\". To make it look\n   // smoothly, the widget padding box is used.\n\n   // TODO Handle inverse drawing the same way as in drawWidgetBox?\n   // Maybe this method (drawBox) is anyway obsolete when extraSpace\n   // is fully supported (as here, in the \"dillo_grows\" repository).\n\n   int xPad, yPad, widthPad, heightPad;\n   getPaddingArea (&xPad, &yPad, &widthPad, &heightPad);\n   style::drawBackground\n      (view, layout, &canvasArea,\n       allocation.x + x + style->margin.left + style->borderWidth.left,\n       allocation.y + y + style->margin.top + style->borderWidth.top,\n       width - style->margin.left - style->borderWidth.left\n       - style->margin.right - style->borderWidth.right,\n       height - style->margin.top - style->borderWidth.top\n       - style->margin.bottom - style->borderWidth.bottom,\n       xPad, yPad, widthPad, heightPad, style, style->backgroundColor,\n       inverse, false);\n}\n\n/**\n * \\brief Draw borders and background of a widget.\n *\n * area is given in widget coordinates.\n *\n */\nvoid Widget::drawWidgetBox (View *view, Rectangle *area, bool inverse)\n{\n   Rectangle canvasArea;\n   canvasArea.x = area->x + allocation.x;\n   canvasArea.y = area->y + allocation.y;\n   canvasArea.width = area->width;\n   canvasArea.height = area->height;\n\n   int xMar, yMar, widthMar, heightMar;\n   getMarginArea (&xMar, &yMar, &widthMar, &heightMar);\n   style::drawBorder (view, layout, &canvasArea, xMar, yMar, widthMar,\n                      heightMar, style, inverse);\n\n   int xPad, yPad, widthPad, heightPad;\n   getPaddingArea (&xPad, &yPad, &widthPad, &heightPad);\n\n   style::Color *bgColor;\n   if (inverse && style->backgroundColor == NULL) {\n      // See style::drawBackground: for inverse drawing, we need a\n      // defined background color. Search through ancestors.\n      Widget *w = this;\n      while (w != NULL && w->style->backgroundColor == NULL)\n         w = w->parent;\n      \n      if (w != NULL && w->style->backgroundColor != NULL)\n         bgColor = w->style->backgroundColor;\n      else\n         bgColor = layout->getBgColor ();\n   } else\n      bgColor = style->backgroundColor;\n\n   style::drawBackground (view, layout, &canvasArea,\n                          xPad, yPad, widthPad, heightPad,\n                          xPad, yPad, widthPad, heightPad,\n                          style, bgColor, inverse, parent == NULL);\n}\n\n/*\n * This function is used by some widgets, when they are selected (as a whole).\n *\n * \\todo This could be accelerated by using clipping bitmaps. Two important\n * issues:\n *\n *     (i) There should always been a pixel in the upper-left corner of the\n *         *widget*, so probably two different clipping bitmaps have to be\n *         used (10/01 and 01/10).\n *\n *    (ii) Should a new GC always be created?\n *\n * \\bug Not implemented.\n */\nvoid Widget::drawSelected (View *view, Rectangle *area)\n{\n}\n\n\nvoid Widget::setButtonSensitive (bool buttonSensitive)\n{\n   this->buttonSensitive = buttonSensitive;\n   buttonSensitiveSet = true;\n}\n\n\n/**\n * \\brief Get the widget at the root of the tree, this widget is part from.\n */\nWidget *Widget::getTopLevel ()\n{\n   Widget *widget = this;\n\n   while (widget->parent)\n      widget = widget->parent;\n\n   return widget;\n}\n\n/**\n * \\brief Get the level of the widget within the tree.\n *\n * The root widget has the level 0.\n */\nint Widget::getLevel ()\n{\n   Widget *widget = this;\n   int level = 0;\n\n   while (widget->parent) {\n      level++;\n      widget = widget->parent;\n   }\n\n   return level;\n}\n\n/**\n * \\brief Get the level of the widget within the tree, regarting the\n * generators, not the parents.\n *\n * The root widget has the level 0.\n */\nint Widget::getGeneratorLevel ()\n{\n   Widget *widget = this;\n   int level = 0;\n\n   while (widget->getGenerator ()) {\n      level++;\n      widget = widget->getGenerator ();\n   }\n\n   return level;\n}\n\n/**\n * \\brief Get the widget with the highest level, which is a direct ancestor of\n *    widget1 and widget2.\n */\nWidget *Widget::getNearestCommonAncestor (Widget *otherWidget)\n{\n   Widget *widget1 = this, *widget2 = otherWidget;\n   int level1 = widget1->getLevel (), level2 = widget2->getLevel();\n\n   /* Get both widgets onto the same level.*/\n   while (level1 > level2) {\n      widget1 = widget1->parent;\n      level1--;\n   }\n\n   while (level2 > level1) {\n      widget2 = widget2->parent;\n      level2--;\n   }\n\n   /* Search upwards. */\n   while (widget1 != widget2) {\n      if (widget1->parent == NULL) {\n         MSG_WARN(\"widgets in different trees\\n\");\n         return NULL;\n      }\n\n      widget1 = widget1->parent;\n      widget2 = widget2->parent;\n   }\n\n   return widget1;\n}\n\nvoid Widget::scrollTo (HPosition hpos, VPosition vpos,\n               int x, int y, int width, int height)\n{\n   layout->scrollTo (hpos, vpos,\n                     x + allocation.x, y + allocation.y, width, height);\n}\n\nvoid Widget::getMarginArea (int *xMar, int *yMar, int *widthMar, int *heightMar)\n{\n   *xMar = allocation.x + extraSpace.left;\n   *yMar = allocation.y + extraSpace.top;\n   *widthMar = allocation.width - (extraSpace.left + extraSpace.right);\n   *heightMar = getHeight () - (extraSpace.top + extraSpace.bottom);\n}\n\nvoid Widget::getBorderArea (int *xBor, int *yBor, int *widthBor, int *heightBor)\n{\n   getMarginArea (xBor, yBor, widthBor, heightBor);\n\n   *xBor += style->margin.left;\n   *yBor += style->margin.top;\n   *widthBor -= style->margin.left + style->margin.right;\n   *heightBor -= style->margin.top + style->margin.bottom;\n}\n\n/**\n * \\brief Return the padding area (content plus padding).\n *\n * Used as \"reference area\" (ee comment of \"style::drawBackground\")\n * for backgrounds.\n */\nvoid Widget::getPaddingArea (int *xPad, int *yPad, int *widthPad,\n                             int *heightPad)\n{\n   getBorderArea (xPad, yPad, widthPad, heightPad);\n\n   *xPad += style->borderWidth.left;\n   *yPad += style->borderWidth.top;\n   *widthPad -= style->borderWidth.left + style->borderWidth.right;\n   *heightPad -= style->borderWidth.top + style->borderWidth.bottom;\n}\n\nvoid Widget::sizeRequestImpl (Requisition *requisition, int numPos,\n                              Widget **references, int *x, int *y)\n{\n   // Use the simple variant.\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"Widget::sizeRequestImpl\");\n   sizeRequestSimpl (requisition);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Widget::sizeRequestSimpl (Requisition *requisition)\n{\n   // Either variant should be implemented.\n   misc::notImplemented (\"Widget::sizeRequestSimpl\");\n}\n\nvoid Widget::getExtremesImpl (Extremes *extremes, int numPos,\n                              Widget **references, int *x, int *y)\n{\n   // Use the simple variant.\n   DBG_OBJ_ENTER0 (\"resize\", 0, \"Widget::getExtremesImpl\");\n   getExtremesSimpl (extremes);\n   DBG_OBJ_LEAVE ();\n}\n\nvoid Widget::getExtremesSimpl (Extremes *extremes)\n{\n    // Either variant should be implemented.\n   misc::notImplemented (\"Widget::getExtremesSimpl\");\n}\n\nvoid Widget::sizeAllocateImpl (Allocation *allocation)\n{\n}\n\n/**\n * \\brief The actual implementation for calculating\n *    dw::core::Widget::extraSpace.\n *\n * The implementation gets a clean value of\n * dw::core::Widget::extraSpace, which is only corrected. To make sure\n * all possible influences are considered, the implementation of the\n * base class should be called, too.\n */\nvoid Widget::calcExtraSpaceImpl (int numPos, Widget **references, int *x,\n                                 int *y)\n{\n}\n\nvoid Widget::markSizeChange (int ref)\n{\n}\n\nvoid Widget::markExtremesChange (int ref)\n{\n}\n\nint Widget::applyPerWidth (int containerWidth, style::Length perWidth)\n{\n   return style::multiplyWithPerLength (containerWidth, perWidth)\n      + boxDiffWidth ();\n}\n\nint Widget::applyPerHeight (int containerHeight, style::Length perHeight)\n{\n   return style::multiplyWithPerLength (containerHeight, perHeight)\n      + boxDiffHeight ();\n}\n\nint Widget::getAvailWidthOfChild (Widget *child, bool forceValue)\n{\n   // This is a halfway suitable implementation for all\n   // containers. For simplification, this will be used during the\n   // development; then, a differentiation could be possible.\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"getAvailWidthOfChild\", \"%p, %s\",\n                  child, forceValue ? \"true\" : \"false\");\n\n   int width;\n\n   if (child->getStyle()->width == style::LENGTH_AUTO) {\n      DBG_OBJ_MSG (\"resize\", 1, \"no specification\");\n      if (forceValue)\n         width = misc::max (getAvailWidth (true) - boxDiffWidth (), 0);\n      else\n         width = -1;\n   } else {\n      // In most cases, the toplevel widget should be a container, so\n      // the container is non-NULL when the parent is non-NULL. Just\n      // in case, regard also parent. And quasiParent.\n      Widget *effContainer = child->quasiParent ? child->quasiParent :\n         (child->container ? child->container : child->parent);\n\n      if (effContainer == this) {\n         width = -1;\n         child->calcFinalWidth (child->getStyle(), -1, this, 0, forceValue,\n                                &width);\n      } else {\n         DBG_OBJ_MSG (\"resize\", 1, \"delegated to (effective) container\");\n         DBG_OBJ_MSG_START ();\n         width = effContainer->getAvailWidthOfChild (child, forceValue);\n         DBG_OBJ_MSG_END ();\n      }\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", width);\n   return width;\n}\n\nint Widget::getAvailHeightOfChild (Widget *child, bool forceValue)\n{\n   // Again, a suitable implementation for all widgets (perhaps).\n\n   // TODO Consider 'min-height' and 'max-height'. (Minor priority, as long as\n   //      \"getAvailHeight (true)\" is not used.\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"getAvailHeightOfChild\", \"%p, %s\",\n                  child, forceValue ? \"true\" : \"false\");\n\n   int height;\n\n   if (child->getStyle()->height == style::LENGTH_AUTO) {\n      DBG_OBJ_MSG (\"resize\", 1, \"no specification\");\n      if (forceValue)\n         height = misc::max (getAvailHeight (true) - boxDiffHeight (), 0);\n      else\n         height = -1;\n   } else {\n      // See comment in Widget::getAvailWidthOfChild.\n      Widget *effContainer = child->quasiParent ? child->quasiParent :\n         (child->container ? child->container : child->parent);\n\n      if (effContainer == this) {\n         if (style::isAbsLength (child->getStyle()->height)) {\n            DBG_OBJ_MSGF (\"resize\", 1, \"absolute height: %dpx\",\n                          style::absLengthVal (child->getStyle()->height));\n            height = misc::max (style::absLengthVal (child->getStyle()->height)\n                               + child->boxDiffHeight (), 0);\n         } else {\n            assert (style::isPerLength (child->getStyle()->height));\n            DBG_OBJ_MSGF (\"resize\", 1, \"percentage height: %g%%\",\n                          100 * style::perLengthVal_useThisOnlyForDebugging\n                          (child->getStyle()->height));\n\n            int availHeight = getAvailHeight (forceValue);\n            if (availHeight == -1)\n               height = -1;\n            else\n               height =\n                  misc::max (child->applyPerHeight (availHeight -\n                                                    boxDiffHeight (),\n                                                    child->getStyle()->height),\n                             0);\n         }\n      } else {\n         DBG_OBJ_MSG (\"resize\", 1, \"delegated to (effective) container\");\n         DBG_OBJ_MSG_START ();\n         height = effContainer->getAvailHeightOfChild (child, forceValue);\n         DBG_OBJ_MSG_END ();\n      }\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d\", height);\n   return height;\n}\n\nvoid Widget::correctRequisitionOfChild (Widget *child, Requisition *requisition,\n                                        void (*splitHeightFun) (int, int*,\n                                                                int*),\n                                        bool allowDecreaseWidth,\n                                        bool allowDecreaseHeight)\n{\n   // Again, a suitable implementation for all widgets (perhaps).\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"correctRequisitionOfChild\",\n                  \"%p, %d * (%d + %d), ..., %s, %s\", child, requisition->width,\n                  requisition->ascent, requisition->descent,\n                  misc::boolToStr (allowDecreaseWidth),\n                  misc::boolToStr (allowDecreaseHeight));\n\n   // See comment in Widget::getAvailWidthOfChild.\n   Widget *effContainer = child->quasiParent ? child->quasiParent :\n      (child->container ? child->container : child->parent);\n\n   if (effContainer == this) {\n      correctReqWidthOfChild (child, requisition, allowDecreaseWidth);\n      correctReqHeightOfChild (child, requisition, splitHeightFun,\n                               allowDecreaseHeight);\n   } else {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to (effective) container\");\n      DBG_OBJ_MSG_START ();\n      effContainer->correctRequisitionOfChild (child, requisition,\n                                               splitHeightFun,\n                                               allowDecreaseWidth,\n                                               allowDecreaseHeight);\n      DBG_OBJ_MSG_END ();\n   }\n\n   DBG_OBJ_LEAVE_VAL (\"%d * (%d + %d)\", requisition->width, requisition->ascent,\n                      requisition->descent);\n}\n\nvoid Widget::correctReqWidthOfChild (Widget *child, Requisition *requisition,\n                                     bool allowDecreaseWidth)\n{\n   DBG_OBJ_ENTER (\"resize\", 0, \"correctReqWidthOfChild\",\n                  \"%p, %d * (%d + %d), %s\",\n                  child, requisition->width, requisition->ascent,\n                  requisition->descent, misc::boolToStr (allowDecreaseWidth));\n\n   assert (this == child->quasiParent || this == child->container);\n\n   int limitMinWidth = child->getMinWidth (NULL, true);\n   if (!allowDecreaseWidth && limitMinWidth < requisition->width)\n      limitMinWidth = requisition->width;\n\n   child->calcFinalWidth (child->getStyle(), -1, this, limitMinWidth, false,\n                          &requisition->width);\n\n   DBG_OBJ_LEAVE_VAL (\"%d * (%d + %d)\", requisition->width, requisition->ascent,\n                      requisition->descent);\n}\n\nvoid Widget::correctReqHeightOfChild (Widget *child, Requisition *requisition,\n                                      void (*splitHeightFun) (int, int*, int*),\n                                      bool allowDecreaseHeight)\n{\n   // TODO Correct height by extremes? (Height extemes?)\n\n   assert (this == child->quasiParent || this == child->container);\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"correctReqHeightOfChild\",\n                  \"%p, %d * (%d + %d), ..., %s\", child, requisition->width,\n                  requisition->ascent, requisition->descent,\n                  misc::boolToStr (allowDecreaseHeight));\n\n   int height = child->calcHeight (child->getStyle()->height, false, -1, this,\n                                   false);\n   adjustHeight (&height, allowDecreaseHeight, requisition->ascent,\n                 requisition->descent);\n\n   int minHeight = child->calcHeight (child->getStyle()->minHeight, false, -1,\n                                      this, false);\n   adjustHeight (&minHeight, allowDecreaseHeight, requisition->ascent,\n                 requisition->descent);\n\n   int maxHeight = child->calcHeight (child->getStyle()->maxHeight, false, -1,\n                                      this, false);\n   adjustHeight (&maxHeight, allowDecreaseHeight, requisition->ascent,\n                 requisition->descent);\n\n   // TODO Perhaps split first, then add box ascent and descent.\n   if (height != -1)\n      splitHeightFun (height, &requisition->ascent, &requisition->descent);\n   if (minHeight != -1 &&\n       requisition->ascent + requisition->descent < minHeight)\n      splitHeightFun (minHeight, &requisition->ascent,\n                      &requisition->descent);\n   if (maxHeight != -1 &&\n       requisition->ascent + requisition->descent > maxHeight)\n      splitHeightFun (maxHeight, &requisition->ascent,\n                      &requisition->descent);\n\n   DBG_OBJ_LEAVE_VAL (\"%d * (%d + %d)\", requisition->width, requisition->ascent,\n                      requisition->descent);\n}\n\nvoid Widget::correctExtremesOfChild (Widget *child, Extremes *extremes,\n                                     bool useAdjustmentWidth)\n{\n   // See comment in correctRequisitionOfChild.\n\n   DBG_OBJ_ENTER (\"resize\", 0, \"correctExtremesOfChild\",\n                  \"%p, %d (%d) / %d (%d)\",\n                  child, extremes->minWidth, extremes->minWidthIntrinsic,\n                  extremes->maxWidth, extremes->maxWidthIntrinsic);\n\n   // See comment in Widget::getAvailWidthOfChild.\n   Widget *effContainer = child->quasiParent ? child->quasiParent :\n      (child->container ? child->container : child->parent);\n\n   if (effContainer == this) {\n      int limitMinWidth =\n         useAdjustmentWidth ? child->getMinWidth (extremes, false) : 0;\n      int width = child->calcWidth (child->getStyle()->width, -1, this,\n                                    limitMinWidth, false);\n      int minWidth = child->calcWidth (child->getStyle()->minWidth, -1, this,\n                                       limitMinWidth, false);\n      int maxWidth = child->calcWidth (child->getStyle()->maxWidth, -1, this,\n                                       limitMinWidth, false);\n\n      DBG_OBJ_MSGF (\"resize\", 1, \"width = %d, minWidth = %d, maxWidth = %d\",\n                    width, minWidth, maxWidth);\n\n      if (width != -1)\n         extremes->minWidth = extremes->maxWidth = width;\n      if (minWidth != -1)\n         extremes->minWidth = minWidth;\n      if (maxWidth != -1)\n         extremes->maxWidth = maxWidth;\n   } else {\n      DBG_OBJ_MSG (\"resize\", 1, \"delegated to (effective) container\");\n      DBG_OBJ_MSG_START ();\n      effContainer->correctExtremesOfChild (child, extremes,\n                                            useAdjustmentWidth);\n      DBG_OBJ_MSG_END ();\n   }\n\n\n   DBG_OBJ_LEAVE_VAL (\"%d / %d\", extremes->minWidth, extremes->maxWidth);\n}\n\n/**\n * \\brief This method is called after a widget has been set as the top of a\n *    widget tree.\n *\n * A widget may override this method when it is necessary to be notified.\n */\nvoid Widget::notifySetAsTopLevel()\n{\n}\n\n/**\n * \\brief This method is called after a widget has been added to a parent.\n *\n * A widget may override this method when it is necessary to be notified.\n */\nvoid Widget::notifySetParent()\n{\n}\n\nbool Widget::isBlockLevel ()\n{\n   // Most widgets are not block-level.\n   return false;\n}\n\nbool Widget::isPossibleContainer ()\n{\n   // In most (all?) cases identical to:\n   return isBlockLevel ();\n}\n\nbool Widget::buttonPressImpl (EventButton *event)\n{\n   return false;\n}\n\nbool Widget::buttonReleaseImpl (EventButton *event)\n{\n   return false;\n}\n\nbool Widget::motionNotifyImpl (EventMotion *event)\n{\n   return false;\n}\n\nvoid Widget::enterNotifyImpl (EventCrossing *)\n{\n   style::Tooltip *tooltip = getStyle()->x_tooltip;\n\n   if (tooltip)\n      tooltip->onEnter();\n}\n\nvoid Widget::leaveNotifyImpl (EventCrossing *)\n{\n   style::Tooltip *tooltip = getStyle()->x_tooltip;\n\n   if (tooltip)\n      tooltip->onLeave();\n}\n\n\nvoid Widget::removeChild (Widget *child)\n{\n   // Should be implemented.\n   misc::notImplemented (\"Widget::removeChild\");\n}\n\n// ----------------------------------------------------------------------\n\nvoid splitHeightPreserveAscent (int height, int *ascent, int *descent)\n{\n   DBG_OBJ_ENTER_S (\"resize\", 1, \"splitHeightPreserveAscent\", \"%d, %d, %d\",\n                    height, *ascent, *descent);\n   \n   *descent = height - *ascent;\n   if (*descent < 0) {\n      *descent = 0;\n      *ascent = height;\n   }\n\n   DBG_OBJ_LEAVE_VAL_S (\"%d, %d\", *ascent, *descent);\n}\n\nvoid splitHeightPreserveDescent (int height, int *ascent, int *descent)\n{\n   DBG_OBJ_ENTER_S (\"resize\", 1, \"splitHeightPreserveDescent\", \"%d, %d, %d\",\n                    height, *ascent, *descent);\n\n   *ascent = height - *descent;\n   if (*ascent < 0) {\n      *ascent = 0;\n      *descent = height;\n   }\n\n   DBG_OBJ_LEAVE_VAL_S (\"%d, %d\", *ascent, *descent);\n}\n\n} // namespace core\n} // namespace dw\n"
  },
  {
    "path": "dw/widget.hh",
    "content": "#ifndef __DW_WIDGET_HH__\n#define __DW_WIDGET_HH__\n\n#ifndef __INCLUDED_FROM_DW_CORE_HH__\n#   error Do not include this file directly, use \"core.hh\" instead.\n#endif\n\n#include \"../lout/identity.hh\"\n\n/**\n * \\brief The type for callback functions.\n */\ntypedef void (*DW_Callback_t)(void *data);\n\nnamespace dw {\nnamespace core {\n\n/**\n * \\brief The base class of all dillo widgets.\n *\n * \\sa\\ref dw-overview, \\ref dw-layout-widgets\n */\nclass Widget: public lout::identity::IdentifiableObject\n{\n   friend class Layout;\n\nprotected:\n   enum Flags {\n      /**\n       * \\todo Comment this.\n       */\n      RESIZE_QUEUED    = 1 << 0,\n\n      /**\n       * \\todo Comment this.\n       */\n      EXTREMES_QUEUED  = 1 << 1,\n\n      /**\n       * \\brief Set, when dw::core::Widget::requisition is not up to date\n       *    anymore.\n       *\n       * \\todo Update, see RESIZE_QUEUED.\n       */\n      NEEDS_RESIZE     = 1 << 2,\n\n      /**\n       * \\brief Only used internally, set to enforce size allocation.\n       *\n       * In some cases, the size of a widget remains the same, but the\n       * children are allocated at different positions and in\n       * different sizes, so that a simple comparison of old and new\n       * allocation is insufficient. Therefore, this flag is set\n       * (indirectly, as ALLOCATE_QUEUED) in queueResize.\n       */\n      NEEDS_ALLOCATE   = 1 << 3,\n\n      /**\n       * \\todo Comment this.\n       */\n      ALLOCATE_QUEUED  = 1 << 4,\n\n      /**\n       * \\brief Set, when dw::core::Widget::extremes is not up to date\n       *    anymore.\n       *\n       * \\todo Update, see RESIZE_QUEUED.\n       */\n      EXTREMES_CHANGED = 1 << 5,\n\n      /**\n       * \\brief Set, when a widget was already once allocated,\n       *\n       * The dw::Image widget uses this flag, see dw::Image::setBuffer.\n       */\n      WAS_ALLOCATED    = 1 << 6,\n   };\n\n   /**\n    * \\brief Implementation which represents the whole widget.\n    *\n    * The only instance is set created needed.\n    */\n   class WidgetImgRenderer: public style::StyleImage::ExternalWidgetImgRenderer\n   {\n   private:\n      Widget *widget;\n\n   public:\n      inline WidgetImgRenderer (Widget *widget) { this->widget = widget; }\n\n      bool readyToDraw ();\n      void getBgArea (int *x, int *y, int *width, int *height);\n      void getRefArea (int *xRef, int *yRef, int *widthRef, int *heightRef);\n      style::Style *getStyle ();\n      void draw (int x, int y, int width, int height);\n   };\n\n   WidgetImgRenderer *widgetImgRenderer;\n\nprivate:\n   static bool adjustMinWidth;\n\n   /**\n    * \\brief The parent widget, NULL for top-level widgets.\n    */\n   Widget *parent;\n\n   /**\n    * \\brief ...\n    */\n   Widget *quasiParent;\n\n   /**\n    * \\brief The generating widget, NULL for top-level widgets, or if\n    *    not set; in the latter case, the effective generator (see\n    *    getGenerator) is the parent.\n    */\n   Widget *generator;\n\n   /**\n    * \\brief The containing widget, equivalent to the \"containing\n    *    block\" defined by CSS. May be NULL, in this case the viewport\n    *    is used.\n    */\n   Widget *container;\n\n   WidgetReference *widgetReference;\n   \n   style::Style *style;\n\n   Flags flags;\n\n   /**\n    * \\brief Size_request() stores the result of the last call of\n    *    size_request_impl().\n    *\n    * Do not read this directly, but call size_request().\n    */\n   Requisition requisition;\n   SizeParams requisitionParams;\n\n   /**\n    * \\brief Analogue to dw::core::Widget::requisition.\n    */\n   Extremes extremes;\n   SizeParams extremesParams;\n\n   /**\n    * \\brief See dw::core::Widget::setBgColor().\n    */\n   style::Color *bgColor;\n\n   /**\n    * \\brief See dw::core::Widget::setButtonSensitive().\n    */\n   bool buttonSensitive;\n\n   /**\n    * \\brief See dw::core::Widget::setButtonSensitive().\n    */\n   bool buttonSensitiveSet;\n\n   void queueResize (int ref, bool extremesChanged, bool fast);\n   inline void queueResizeFast (int ref, bool extremesChanged)\n   { queueResize (ref, extremesChanged, true); }\n\npublic:\n   /**\n    * \\brief This value is defined by the parent widget, and used for\n    *    incremential resizing.\n    *\n    * See documentation for an explanation.\n    */\n   int parentRef;\n\nprotected:\n\n   /**\n    * \\brief The current allocation: size and position, always relative to the\n    *    canvas.\n    */\n   Allocation allocation;\n\n   inline int getHeight () { return allocation.ascent + allocation.descent; }\n   inline int getContentWidth() { return allocation.width - boxDiffWidth (); }\n   inline int getContentHeight() { return getHeight () - boxDiffHeight (); }\n\n   Layout *layout;\n\n   /**\n    * \\brief Space around the margin box. Allocation is extraSpace +\n    *    margin + border + padding + contents.\n    *\n    * See also dw::core::Widget::calcExtraSpace and\n    * dw::core::Widget::calcExtraSpaceImpl. Also, it is feasible to\n    * correct this value within dw::core::Widget::sizeRequestImpl.\n    */\n   style::Box extraSpace;\n\n   /**\n    * \\brief Set iff this widget constitutes a stacking context, as defined by\n    *    CSS.\n    */\n   StackingContextMgr *stackingContextMgr;\n\n   /**\n    * \\brief The bottom-most ancestor (or this) for which stackingContextMgr is\n    *    set.\n    */\n   Widget *stackingContextWidget;\n\n   inline StackingContextMgr *getNextStackingContextMgr ()\n   { return stackingContextWidget->stackingContextMgr; }\n\n   /*inline void printFlags () {\n      DBG_IF_RTFL {\n         char buf[10 * 3 - 1 + 1];\n         snprintf (buf, sizeof (buf), \"%s:%s:%s:%s:%s:%s:%s\",\n                   (flags & RESIZE_QUEUED)    ? \"Rq\" : \"--\",\n                   (flags & EXTREMES_QUEUED)  ? \"Eq\" : \"--\",\n                   (flags & NEEDS_RESIZE)     ? \"nR\" : \"--\",\n                   (flags & NEEDS_ALLOCATE)   ? \"nA\" : \"--\",\n                   (flags & ALLOCATE_QUEUED)  ? \"Aq\" : \"--\",\n                   (flags & EXTREMES_CHANGED) ? \"Ec\" : \"--\",\n                   (flags & WAS_ALLOCATED)    ? \"wA\" : \"--\");\n         DBG_OBJ_SET_SYM (\"flags\", buf);\n      }\n   }*/\n\n   inline void printFlag (Flags f) {\n      DBG_IF_RTFL {\n         switch (f) {\n         case RESIZE_QUEUED:\n               DBG_OBJ_SET_SYM (\"flags.RESIZE_QUEUED\",\n                                (flags & RESIZE_QUEUED) ? \"true\" : \"false\");\n               break;\n\n         case EXTREMES_QUEUED:\n            DBG_OBJ_SET_SYM (\"flags.EXTREMES_QUEUED\",\n                             (flags & EXTREMES_QUEUED) ? \"true\" : \"false\");\n            break;\n\n         case NEEDS_RESIZE:\n            DBG_OBJ_SET_SYM (\"flags.NEEDS_RESIZE\",\n                             (flags & NEEDS_RESIZE) ? \"true\" : \"false\");\n            break;\n\n         case NEEDS_ALLOCATE:\n            DBG_OBJ_SET_SYM (\"flags.NEEDS_ALLOCATE\",\n                             (flags & NEEDS_ALLOCATE) ? \"true\" : \"false\");\n            break;\n\n         case ALLOCATE_QUEUED:\n            DBG_OBJ_SET_SYM (\"flags.ALLOCATE_QUEUED\",\n                             (flags & ALLOCATE_QUEUED) ? \"true\" : \"false\");\n            break;\n\n         case EXTREMES_CHANGED:\n            DBG_OBJ_SET_SYM (\"flags.EXTREMES_CHANGED\",\n                             (flags & EXTREMES_CHANGED) ? \"true\" : \"false\");\n            break;\n\n         case WAS_ALLOCATED:\n            DBG_OBJ_SET_SYM (\"flags.WAS_ALLOCATED\",\n                             (flags & WAS_ALLOCATED) ? \"true\" : \"false\");\n            break;\n         }\n      }\n   }\n\n   inline void setFlags (Flags f)\n   { flags = (Flags)(flags | f); printFlag (f); }\n   inline void unsetFlags (Flags f)\n   { flags = (Flags)(flags & ~f); printFlag (f); }\n\n   inline void queueDraw ()\n   { queueDrawArea (0, 0, allocation.width, getHeight()); }\n   void queueDrawArea (int x, int y, int width, int height);\n   inline void queueResize (int ref, bool extremesChanged)\n   { queueResize (ref, extremesChanged, false); }\n\n   /**\n    * \\brief See \\ref dw-widget-sizes.\n    */\n   virtual void sizeRequestImpl (Requisition *requisition, int numPos,\n                                 Widget **references, int *x, int *y);\n\n   /**\n    * \\brief Simple variant, to be implemented by widgets with sizes\n    *    not depending on positions.\n    */\n   virtual void sizeRequestSimpl (Requisition *requisition);\n\n   /**\n    * \\brief See \\ref dw-widget-sizes.\n    */\n   virtual void getExtremesImpl (Extremes *extremes, int numPos,\n                                 Widget **references, int *x, int *y);\n\n   /**\n    * \\brief Simple variant, to be implemented by widgets with\n    *    extremes not depending on positions.\n    */\n   virtual void getExtremesSimpl (Extremes *extremes);\n\n   virtual void calcExtraSpaceImpl (int numPos, Widget **references, int *x,\n                                    int *y);\n\n   /**\n    * \\brief See \\ref dw-widget-sizes.\n    */\n   virtual void sizeAllocateImpl (Allocation *allocation);\n\n   /**\n    * \\brief Called after sizeAllocateImpl() to redraw necessary areas.\n    * By default the whole widget is redrawn.\n    */\n   virtual void resizeDrawImpl () { queueDraw (); };\n\n   /**\n    * \\brief See \\ref dw-widget-sizes.\n    */\n   virtual void markSizeChange (int ref);\n\n   /**\n    * \\brief See \\ref dw-widget-sizes.\n    */\n   virtual void markExtremesChange (int ref);\n\n   virtual int getAvailWidthOfChild (Widget *child, bool forceValue);\n   virtual int getAvailHeightOfChild (Widget *child, bool forceValue);\n   virtual void correctRequisitionOfChild (Widget *child,\n                                           Requisition *requisition,\n                                           void (*splitHeightFun) (int, int*,\n                                                                   int*),\n                                           bool allowDecreaseWidth,\n                                           bool allowDecreaseHeight);\n   void correctReqWidthOfChild (Widget *child, Requisition *requisition,\n                                bool allowDecreaseWidth);\n   void correctReqHeightOfChild (Widget *child, Requisition *requisition,\n                                 void (*splitHeightFun) (int, int*, int*),\n                                 bool allowDecreaseHeight);\n   virtual void correctExtremesOfChild (Widget *child, Extremes *extremes,\n                                        bool useAdjustmentWidth);\n\n   virtual void containerSizeChangedForChildren ();\n\n   virtual bool affectedByContainerSizeChange ();\n   virtual bool affectsSizeChangeContainerChild (Widget *child);\n   virtual bool usesAvailWidth ();\n   virtual bool usesAvailHeight ();\n\n   virtual void notifySetAsTopLevel();\n   virtual void notifySetParent();\n\n   virtual bool buttonPressImpl (EventButton *event);\n   virtual bool buttonReleaseImpl (EventButton *event);\n   virtual bool motionNotifyImpl (EventMotion *event);\n   virtual void enterNotifyImpl (EventCrossing *event);\n   virtual void leaveNotifyImpl (EventCrossing *event);\n\n   inline char *addAnchor (const char* name)\n   { return layout->addAnchor (this, name); }\n\n   inline char *addAnchor (const char* name, int y)\n   { return layout->addAnchor (this, name, y); }\n\n   inline void changeAnchor (char* name, int y)\n   { layout->changeAnchor (this, name, y); }\n\n   inline void removeAnchor (char* name)\n   { if (layout) layout->removeAnchor (this, name); }\n\n   //inline void updateBgColor () { layout->updateBgColor (); }\n\n   inline void setCursor (style::Cursor cursor)\n   { layout->setCursor (cursor);  }\n#if 0\n   inline bool selectionButtonPress (Iterator *it, int charPos, int linkNo,\n                                     EventButton *event, bool withinContent)\n   { return layout->selectionState.buttonPress (it, charPos, linkNo, event); }\n\n   inline bool selectionButtonRelease (Iterator *it, int charPos, int linkNo,\n                                       EventButton *event, bool withinContent)\n   { return layout->selectionState.buttonRelease (it, charPos, linkNo, event);}\n\n   inline bool selectionButtonMotion (Iterator *it, int charPos, int linkNo,\n                                      EventMotion *event, bool withinContent)\n   { return layout->selectionState.buttonMotion (it, charPos, linkNo, event); }\n#endif\n   inline bool selectionHandleEvent (SelectionState::EventType eventType,\n                                     Iterator *it, int charPos, int linkNo,\n                                     MousePositionEvent *event)\n   { return layout->selectionState.handleEvent (eventType, it, charPos, linkNo,\n                                                event); }\n\nprivate:\n   void *deleteCallbackData;\n   DW_Callback_t deleteCallbackFunc;\n\npublic:\n   inline void setDeleteCallback(DW_Callback_t func, void *data)\n   { deleteCallbackFunc = func; deleteCallbackData = data; }\n\nprivate:\n   bool resizeIdleEntered () { return layout && layout->resizeIdleCounter; }\n\n   void enterQueueResize () { if (layout) layout->queueResizeCounter++; }\n   void leaveQueueResize () { if (layout) layout->queueResizeCounter--; }\n   bool queueResizeEntered () { return layout && layout->queueResizeCounter; }\n\n   void enterSizeAllocate () { if (layout) layout->sizeAllocateCounter++; }\n   void leaveSizeAllocate () { if (layout) layout->sizeAllocateCounter--; }\n   bool sizeAllocateEntered () { return layout && layout->sizeAllocateCounter; }\n\n   void enterSizeRequest () { if (layout) layout->sizeRequestCounter++; }\n   void leaveSizeRequest () { if (layout) layout->sizeRequestCounter--; }\n   bool sizeRequestEntered () { return layout && layout->sizeRequestCounter; }\n\n   void enterGetExtremes () { if (layout) layout->getExtremesCounter++; }\n   void leaveGetExtremes () { if (layout) layout->getExtremesCounter--; }\n   bool getExtremesEntered () { return layout && layout->getExtremesCounter; }\n\n\npublic:\n   static int CLASS_ID;\n\n   inline static void setAdjustMinWidth (bool adjustMinWidth)\n   { Widget::adjustMinWidth = adjustMinWidth; }\n\n   Widget ();\n   ~Widget ();\n\n   inline bool resizeQueued ()    { return flags & RESIZE_QUEUED; }\n   inline bool extremesQueued ()  { return flags & EXTREMES_QUEUED; }\n   inline bool needsResize ()     { return flags & NEEDS_RESIZE; }\n   inline bool needsAllocate ()   { return flags & NEEDS_ALLOCATE; }\n   inline bool allocateQueued ()  { return flags & ALLOCATE_QUEUED; }\n   inline bool extremesChanged () { return flags & EXTREMES_CHANGED; }\n   inline bool wasAllocated ()    { return flags & WAS_ALLOCATED; }\n\n   void setParent (Widget *parent);\n   void setQuasiParent (Widget *quasiParent);\n\n   void setGenerator (Widget *generator) { this->generator = generator; }\n\n   inline style::Style *getStyle () { return style; }\n   /** \\todo I do not like this. */\n   inline Allocation *getAllocation () { return &allocation; }\n   inline bool inAllocation (int x, int y) {\n      return wasAllocated () && x >= allocation.x && y >= allocation.y &&\n         x <= allocation.x + allocation.width &&\n         y <= allocation.y + getHeight ();\n   }\n\n   inline int boxOffsetX ()\n   { return extraSpace.left + getStyle()->boxOffsetX (); }\n   inline int boxRestWidth ()\n   { return extraSpace.right + getStyle()->boxRestWidth (); }\n   inline int boxDiffWidth () { return boxOffsetX () + boxRestWidth (); }\n   inline int boxOffsetY ()\n   { return extraSpace.top + getStyle()->boxOffsetY (); }\n   inline int boxRestHeight ()\n   { return extraSpace.bottom + getStyle()->boxRestHeight (); }\n   inline int boxDiffHeight () { return boxOffsetY () + boxRestHeight (); }\n   \n   /**\n    * \\brief See \\ref dw-widget-sizes (or \\ref dw-size-request-pos).\n    */\n   virtual int numSizeRequestReferences ();\n\n   /**\n    * \\brief See \\ref dw-widget-sizes (or \\ref dw-size-request-pos).\n    */\n   virtual Widget *sizeRequestReference (int index);\n\n   /**\n    * \\brief See \\ref dw-widget-sizes (or \\ref dw-size-request-pos).\n    */\n   virtual int numGetExtremesReferences ();\n\n   /**\n    * \\brief See \\ref dw-widget-sizes (or \\ref dw-size-request-pos).\n    */\n   virtual Widget *getExtremesReference (int index);\n\n   void sizeRequest (Requisition *requisition, int numPos = 0,\n                     Widget **references = NULL, int *x = NULL, int *y = NULL);\n   void getExtremes (Extremes *extremes, int numPos = 0,\n                     Widget **references = NULL, int *x = NULL, int *y = NULL);\n   void sizeAllocate (Allocation *allocation);\n\n   void calcExtraSpace (int numPos, Widget **references, int *x, int *y);\n\n   int getAvailWidth (bool forceValue);\n   int getAvailHeight (bool forceValue);\n   virtual bool getAdjustMinWidth () { return Widget::adjustMinWidth; }\n   void correctRequisition (Requisition *requisition,\n                            void (*splitHeightFun) (int, int*, int*),\n                            bool allowDecreaseWidth, bool allowDecreaseHeight);\n   void correctExtremes (Extremes *extremes, bool useAdjustmentWidth);\n   int calcWidth (style::Length cssValue, int refWidth, Widget *refWidget,\n                  int limitMinWidth, bool forceValue);\n   void calcFinalWidth (style::Style *style, int refWidth, Widget *refWidget,\n                        int limitMinWidth, bool forceValue, int *finalWidth);\n   int calcHeight (style::Length cssValue, bool usePercentage, int refHeight,\n                   Widget *refWidget, bool forceValue);\n   static void adjustHeight (int *height, bool allowDecreaseHeight, int ascent,\n                             int descent);\n\n   virtual int applyPerWidth (int containerWidth, style::Length perWidth);\n   virtual int applyPerHeight (int containerHeight, style::Length perHeight);\n\n   int getMinWidth (Extremes *extremes, bool forceValue);\n\n   virtual bool isBlockLevel ();\n   virtual bool isPossibleContainer ();\n\n   void containerSizeChanged ();\n\n   bool intersects (Widget *refWidget, Rectangle *area,\n                    Rectangle *intersection);\n\n   /** Area is given in widget coordinates. */\n   virtual void draw (View *view, Rectangle *area, DrawingContext *context) = 0;\n   void drawInterruption (View *view, Rectangle *area, DrawingContext *context);\n\n   virtual Widget *getWidgetAtPoint (int x, int y,\n                                     GettingWidgetAtPointContext *context);\n   Widget *getWidgetAtPointInterrupted (int x, int y,\n                                        GettingWidgetAtPointContext *context);\n\n   bool buttonPress (EventButton *event);\n   bool buttonRelease (EventButton *event);\n   bool motionNotify (EventMotion *event);\n   void enterNotify (EventCrossing *event);\n   void leaveNotify (EventCrossing *event);\n\n   virtual void setStyle (style::Style *style);\n   void setBgColor (style::Color *bgColor);\n   style::Color *getBgColor ();\n\n   void drawBox (View *view, style::Style *style, Rectangle *area,\n                 int x, int y, int width, int height, bool inverse);\n   void drawWidgetBox (View *view, Rectangle *area, bool inverse);\n   void drawSelected (View *view, Rectangle *area);\n\n   void setButtonSensitive (bool buttonSensitive);\n   inline bool isButtonSensitive () { return buttonSensitive; }\n\n   inline Widget *getParent () { return parent; }\n   inline Widget *getContainer () { return container; }\n   Widget *getTopLevel ();\n   int getLevel ();\n   int getGeneratorLevel ();\n   Widget *getNearestCommonAncestor (Widget *otherWidget);\n\n   inline WidgetReference *getWidgetReference () { return widgetReference; }\n   inline void setWidgetReference (WidgetReference *widgetReference) {\n      this->widgetReference = widgetReference;\n      DBG_OBJ_SET_PTR (\"widgetReference\", widgetReference);\n   }\n   \n   inline Widget *getGenerator () { return generator ? generator : parent; }\n\n   inline Layout *getLayout () { return layout; }\n\n   void scrollTo (HPosition hpos, VPosition vpos,\n                  int x, int y, int width, int height);\n\n   void getMarginArea (int *xMar, int *yMar, int *widthMar, int *heightMar);\n   void getBorderArea (int *xBor, int *yBor, int *widthBor, int *heightBor);\n   void getPaddingArea (int *xPad, int *yPad, int *widthPad, int *heightPad);\n\n   /**\n    * \\brief Return an iterator for this widget.\n    *\n    * \\em mask can narrow the types returned by the iterator, this can\n    * enhance performance quite much, e.g. when only searching for child\n    * widgets.\n    *\n    * With \\em atEnd == false, the iterator starts \\em before the beginning,\n    * i.e. the first call of dw::core::Iterator::next will let the iterator\n    * point on the first piece of contents. Likewise, With \\em atEnd == true,\n    * the iterator starts \\em after the last piece of contents, call\n    * dw::core::Iterator::prev in this case.\n    */\n   virtual Iterator *iterator (Content::Type mask, bool atEnd) = 0;\n\n   virtual void removeChild (Widget *child);\n};\n\nvoid splitHeightPreserveAscent (int height, int *ascent, int *descent);\nvoid splitHeightPreserveDescent (int height, int *ascent, int *descent);\n\n} // namespace core\n} // namespace dw\n\n#endif // __DW_WIDGET_HH__\n"
  },
  {
    "path": "install-dpi-local",
    "content": "#!/bin/sh\n#\n# Install the dpi framework programs inside the user's account.\n#\n\nBASE=\"$HOME/.dillo\"\nBASE2=\"$BASE/dpi\"\n\nif [ -r $BASE/dpi_socket_dir ] ; then\n   rm -r `cat $BASE/dpi_socket_dir`\n   rm $BASE/dpi_socket_dir\nfi\n\nif [ ! -x dpid/dpid-plus ] ; then\n   echo \"This script may only be run AFTER make.\"\n   exit 1\nfi\n\n# Try to communicate with any currently-running dpid to tell it\n# to stop itself and the dpi programs.\ndpidc-plus stop\n\nif [ ! -d $BASE ] ; then\n  mkdir $BASE\nfi\nif [ ! -d $BASE2 ] ; then\n  mkdir $BASE2\nfi\n\ncp dpid/dpid-plus dpid/dpidc-plus dpid/dpidrc $BASE\nstrip $BASE/dpid-plus $BASE/dpidc-plus\n\ncd dpi\nfor F in *.dpi ; do\n   D=\"`echo $F | sed 's/\\..*$//'`\"\n   if [ ! -d $BASE2/$D ] ; then\n      mkdir $BASE2/$D\n   fi\n   cp $F $BASE2/$D\n   strip $BASE2/$D/$F\ndone\ncd ..\n\n"
  },
  {
    "path": "install-sh",
    "content": "#!/bin/sh\n# install - install a program, script, or datafile\n\nscriptversion=2011-01-19.21; # UTC\n\n# This originates from X11R5 (mit/util/scripts/install.sh), which was\n# later released in X11R6 (xc/config/util/install.sh) with the\n# following copyright and license.\n#\n# Copyright (C) 1994 X Consortium\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to\n# deal in the Software without restriction, including without limitation the\n# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n# sell copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE\n# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN\n# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-\n# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n#\n# Except as contained in this notice, the name of the X Consortium shall not\n# be used in advertising or otherwise to promote the sale, use or other deal-\n# ings in this Software without prior written authorization from the X Consor-\n# tium.\n#\n#\n# FSF changes to this file are in the public domain.\n#\n# Calling this script install-sh is preferred over install.sh, to prevent\n# `make' implicit rules from creating a file called install from it\n# when there is no Makefile.\n#\n# This script is compatible with the BSD install script, but was written\n# from scratch.\n\nnl='\n'\nIFS=\" \"\"\t$nl\"\n\n# set DOITPROG to echo to test this script\n\n# Don't use :- since 4.3BSD and earlier shells don't like it.\ndoit=${DOITPROG-}\nif test -z \"$doit\"; then\n  doit_exec=exec\nelse\n  doit_exec=$doit\nfi\n\n# Put in absolute file names if you don't have them in your path;\n# or use environment vars.\n\nchgrpprog=${CHGRPPROG-chgrp}\nchmodprog=${CHMODPROG-chmod}\nchownprog=${CHOWNPROG-chown}\ncmpprog=${CMPPROG-cmp}\ncpprog=${CPPROG-cp}\nmkdirprog=${MKDIRPROG-mkdir}\nmvprog=${MVPROG-mv}\nrmprog=${RMPROG-rm}\nstripprog=${STRIPPROG-strip}\n\nposix_glob='?'\ninitialize_posix_glob='\n  test \"$posix_glob\" != \"?\" || {\n    if (set -f) 2>/dev/null; then\n      posix_glob=\n    else\n      posix_glob=:\n    fi\n  }\n'\n\nposix_mkdir=\n\n# Desired mode of installed file.\nmode=0755\n\nchgrpcmd=\nchmodcmd=$chmodprog\nchowncmd=\nmvcmd=$mvprog\nrmcmd=\"$rmprog -f\"\nstripcmd=\n\nsrc=\ndst=\ndir_arg=\ndst_arg=\n\ncopy_on_change=false\nno_target_directory=\n\nusage=\"\\\nUsage: $0 [OPTION]... [-T] SRCFILE DSTFILE\n   or: $0 [OPTION]... SRCFILES... DIRECTORY\n   or: $0 [OPTION]... -t DIRECTORY SRCFILES...\n   or: $0 [OPTION]... -d DIRECTORIES...\n\nIn the 1st form, copy SRCFILE to DSTFILE.\nIn the 2nd and 3rd, copy all SRCFILES to DIRECTORY.\nIn the 4th, create DIRECTORIES.\n\nOptions:\n     --help     display this help and exit.\n     --version  display version info and exit.\n\n  -c            (ignored)\n  -C            install only if different (preserve the last data modification time)\n  -d            create directories instead of installing files.\n  -g GROUP      $chgrpprog installed files to GROUP.\n  -m MODE       $chmodprog installed files to MODE.\n  -o USER       $chownprog installed files to USER.\n  -s            $stripprog installed files.\n  -t DIRECTORY  install into DIRECTORY.\n  -T            report an error if DSTFILE is a directory.\n\nEnvironment variables override the default commands:\n  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG\n  RMPROG STRIPPROG\n\"\n\nwhile test $# -ne 0; do\n  case $1 in\n    -c) ;;\n\n    -C) copy_on_change=true;;\n\n    -d) dir_arg=true;;\n\n    -g) chgrpcmd=\"$chgrpprog $2\"\n\tshift;;\n\n    --help) echo \"$usage\"; exit $?;;\n\n    -m) mode=$2\n\tcase $mode in\n\t  *' '* | *'\t'* | *'\n'*\t  | *'*'* | *'?'* | *'['*)\n\t    echo \"$0: invalid mode: $mode\" >&2\n\t    exit 1;;\n\tesac\n\tshift;;\n\n    -o) chowncmd=\"$chownprog $2\"\n\tshift;;\n\n    -s) stripcmd=$stripprog;;\n\n    -t) dst_arg=$2\n\t# Protect names problematic for `test' and other utilities.\n\tcase $dst_arg in\n\t  -* | [=\\(\\)!]) dst_arg=./$dst_arg;;\n\tesac\n\tshift;;\n\n    -T) no_target_directory=true;;\n\n    --version) echo \"$0 $scriptversion\"; exit $?;;\n\n    --)\tshift\n\tbreak;;\n\n    -*)\techo \"$0: invalid option: $1\" >&2\n\texit 1;;\n\n    *)  break;;\n  esac\n  shift\ndone\n\nif test $# -ne 0 && test -z \"$dir_arg$dst_arg\"; then\n  # When -d is used, all remaining arguments are directories to create.\n  # When -t is used, the destination is already specified.\n  # Otherwise, the last argument is the destination.  Remove it from $@.\n  for arg\n  do\n    if test -n \"$dst_arg\"; then\n      # $@ is not empty: it contains at least $arg.\n      set fnord \"$@\" \"$dst_arg\"\n      shift # fnord\n    fi\n    shift # arg\n    dst_arg=$arg\n    # Protect names problematic for `test' and other utilities.\n    case $dst_arg in\n      -* | [=\\(\\)!]) dst_arg=./$dst_arg;;\n    esac\n  done\nfi\n\nif test $# -eq 0; then\n  if test -z \"$dir_arg\"; then\n    echo \"$0: no input file specified.\" >&2\n    exit 1\n  fi\n  # It's OK to call `install-sh -d' without argument.\n  # This can happen when creating conditional directories.\n  exit 0\nfi\n\nif test -z \"$dir_arg\"; then\n  do_exit='(exit $ret); exit $ret'\n  trap \"ret=129; $do_exit\" 1\n  trap \"ret=130; $do_exit\" 2\n  trap \"ret=141; $do_exit\" 13\n  trap \"ret=143; $do_exit\" 15\n\n  # Set umask so as not to create temps with too-generous modes.\n  # However, 'strip' requires both read and write access to temps.\n  case $mode in\n    # Optimize common cases.\n    *644) cp_umask=133;;\n    *755) cp_umask=22;;\n\n    *[0-7])\n      if test -z \"$stripcmd\"; then\n\tu_plus_rw=\n      else\n\tu_plus_rw='% 200'\n      fi\n      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;\n    *)\n      if test -z \"$stripcmd\"; then\n\tu_plus_rw=\n      else\n\tu_plus_rw=,u+rw\n      fi\n      cp_umask=$mode$u_plus_rw;;\n  esac\nfi\n\nfor src\ndo\n  # Protect names problematic for `test' and other utilities.\n  case $src in\n    -* | [=\\(\\)!]) src=./$src;;\n  esac\n\n  if test -n \"$dir_arg\"; then\n    dst=$src\n    dstdir=$dst\n    test -d \"$dstdir\"\n    dstdir_status=$?\n  else\n\n    # Waiting for this to be detected by the \"$cpprog $src $dsttmp\" command\n    # might cause directories to be created, which would be especially bad\n    # if $src (and thus $dsttmp) contains '*'.\n    if test ! -f \"$src\" && test ! -d \"$src\"; then\n      echo \"$0: $src does not exist.\" >&2\n      exit 1\n    fi\n\n    if test -z \"$dst_arg\"; then\n      echo \"$0: no destination specified.\" >&2\n      exit 1\n    fi\n    dst=$dst_arg\n\n    # If destination is a directory, append the input filename; won't work\n    # if double slashes aren't ignored.\n    if test -d \"$dst\"; then\n      if test -n \"$no_target_directory\"; then\n\techo \"$0: $dst_arg: Is a directory\" >&2\n\texit 1\n      fi\n      dstdir=$dst\n      dst=$dstdir/`basename \"$src\"`\n      dstdir_status=0\n    else\n      # Prefer dirname, but fall back on a substitute if dirname fails.\n      dstdir=`\n\t(dirname \"$dst\") 2>/dev/null ||\n\texpr X\"$dst\" : 'X\\(.*[^/]\\)//*[^/][^/]*/*$' \\| \\\n\t     X\"$dst\" : 'X\\(//\\)[^/]' \\| \\\n\t     X\"$dst\" : 'X\\(//\\)$' \\| \\\n\t     X\"$dst\" : 'X\\(/\\)' \\| . 2>/dev/null ||\n\techo X\"$dst\" |\n\t    sed '/^X\\(.*[^/]\\)\\/\\/*[^/][^/]*\\/*$/{\n\t\t   s//\\1/\n\t\t   q\n\t\t }\n\t\t /^X\\(\\/\\/\\)[^/].*/{\n\t\t   s//\\1/\n\t\t   q\n\t\t }\n\t\t /^X\\(\\/\\/\\)$/{\n\t\t   s//\\1/\n\t\t   q\n\t\t }\n\t\t /^X\\(\\/\\).*/{\n\t\t   s//\\1/\n\t\t   q\n\t\t }\n\t\t s/.*/./; q'\n      `\n\n      test -d \"$dstdir\"\n      dstdir_status=$?\n    fi\n  fi\n\n  obsolete_mkdir_used=false\n\n  if test $dstdir_status != 0; then\n    case $posix_mkdir in\n      '')\n\t# Create intermediate dirs using mode 755 as modified by the umask.\n\t# This is like FreeBSD 'install' as of 1997-10-28.\n\tumask=`umask`\n\tcase $stripcmd.$umask in\n\t  # Optimize common cases.\n\t  *[2367][2367]) mkdir_umask=$umask;;\n\t  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;\n\n\t  *[0-7])\n\t    mkdir_umask=`expr $umask + 22 \\\n\t      - $umask % 100 % 40 + $umask % 20 \\\n\t      - $umask % 10 % 4 + $umask % 2\n\t    `;;\n\t  *) mkdir_umask=$umask,go-w;;\n\tesac\n\n\t# With -d, create the new directory with the user-specified mode.\n\t# Otherwise, rely on $mkdir_umask.\n\tif test -n \"$dir_arg\"; then\n\t  mkdir_mode=-m$mode\n\telse\n\t  mkdir_mode=\n\tfi\n\n\tposix_mkdir=false\n\tcase $umask in\n\t  *[123567][0-7][0-7])\n\t    # POSIX mkdir -p sets u+wx bits regardless of umask, which\n\t    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.\n\t    ;;\n\t  *)\n\t    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$\n\t    trap 'ret=$?; rmdir \"$tmpdir/d\" \"$tmpdir\" 2>/dev/null; exit $ret' 0\n\n\t    if (umask $mkdir_umask &&\n\t\texec $mkdirprog $mkdir_mode -p -- \"$tmpdir/d\") >/dev/null 2>&1\n\t    then\n\t      if test -z \"$dir_arg\" || {\n\t\t   # Check for POSIX incompatibilities with -m.\n\t\t   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or\n\t\t   # other-writeable bit of parent directory when it shouldn't.\n\t\t   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.\n\t\t   ls_ld_tmpdir=`ls -ld \"$tmpdir\"`\n\t\t   case $ls_ld_tmpdir in\n\t\t     d????-?r-*) different_mode=700;;\n\t\t     d????-?--*) different_mode=755;;\n\t\t     *) false;;\n\t\t   esac &&\n\t\t   $mkdirprog -m$different_mode -p -- \"$tmpdir\" && {\n\t\t     ls_ld_tmpdir_1=`ls -ld \"$tmpdir\"`\n\t\t     test \"$ls_ld_tmpdir\" = \"$ls_ld_tmpdir_1\"\n\t\t   }\n\t\t }\n\t      then posix_mkdir=:\n\t      fi\n\t      rmdir \"$tmpdir/d\" \"$tmpdir\"\n\t    else\n\t      # Remove any dirs left behind by ancient mkdir implementations.\n\t      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null\n\t    fi\n\t    trap '' 0;;\n\tesac;;\n    esac\n\n    if\n      $posix_mkdir && (\n\tumask $mkdir_umask &&\n\t$doit_exec $mkdirprog $mkdir_mode -p -- \"$dstdir\"\n      )\n    then :\n    else\n\n      # The umask is ridiculous, or mkdir does not conform to POSIX,\n      # or it failed possibly due to a race condition.  Create the\n      # directory the slow way, step by step, checking for races as we go.\n\n      case $dstdir in\n\t/*) prefix='/';;\n\t[-=\\(\\)!]*) prefix='./';;\n\t*)  prefix='';;\n      esac\n\n      eval \"$initialize_posix_glob\"\n\n      oIFS=$IFS\n      IFS=/\n      $posix_glob set -f\n      set fnord $dstdir\n      shift\n      $posix_glob set +f\n      IFS=$oIFS\n\n      prefixes=\n\n      for d\n      do\n\ttest X\"$d\" = X && continue\n\n\tprefix=$prefix$d\n\tif test -d \"$prefix\"; then\n\t  prefixes=\n\telse\n\t  if $posix_mkdir; then\n\t    (umask=$mkdir_umask &&\n\t     $doit_exec $mkdirprog $mkdir_mode -p -- \"$dstdir\") && break\n\t    # Don't fail if two instances are running concurrently.\n\t    test -d \"$prefix\" || exit 1\n\t  else\n\t    case $prefix in\n\t      *\\'*) qprefix=`echo \"$prefix\" | sed \"s/'/'\\\\\\\\\\\\\\\\''/g\"`;;\n\t      *) qprefix=$prefix;;\n\t    esac\n\t    prefixes=\"$prefixes '$qprefix'\"\n\t  fi\n\tfi\n\tprefix=$prefix/\n      done\n\n      if test -n \"$prefixes\"; then\n\t# Don't fail if two instances are running concurrently.\n\t(umask $mkdir_umask &&\n\t eval \"\\$doit_exec \\$mkdirprog $prefixes\") ||\n\t  test -d \"$dstdir\" || exit 1\n\tobsolete_mkdir_used=true\n      fi\n    fi\n  fi\n\n  if test -n \"$dir_arg\"; then\n    { test -z \"$chowncmd\" || $doit $chowncmd \"$dst\"; } &&\n    { test -z \"$chgrpcmd\" || $doit $chgrpcmd \"$dst\"; } &&\n    { test \"$obsolete_mkdir_used$chowncmd$chgrpcmd\" = false ||\n      test -z \"$chmodcmd\" || $doit $chmodcmd $mode \"$dst\"; } || exit 1\n  else\n\n    # Make a couple of temp file names in the proper directory.\n    dsttmp=$dstdir/_inst.$$_\n    rmtmp=$dstdir/_rm.$$_\n\n    # Trap to clean up those temp files at exit.\n    trap 'ret=$?; rm -f \"$dsttmp\" \"$rmtmp\" && exit $ret' 0\n\n    # Copy the file name to the temp name.\n    (umask $cp_umask && $doit_exec $cpprog \"$src\" \"$dsttmp\") &&\n\n    # and set any options; do chmod last to preserve setuid bits.\n    #\n    # If any of these fail, we abort the whole thing.  If we want to\n    # ignore errors from any of these, just make sure not to ignore\n    # errors from the above \"$doit $cpprog $src $dsttmp\" command.\n    #\n    { test -z \"$chowncmd\" || $doit $chowncmd \"$dsttmp\"; } &&\n    { test -z \"$chgrpcmd\" || $doit $chgrpcmd \"$dsttmp\"; } &&\n    { test -z \"$stripcmd\" || $doit $stripcmd \"$dsttmp\"; } &&\n    { test -z \"$chmodcmd\" || $doit $chmodcmd $mode \"$dsttmp\"; } &&\n\n    # If -C, don't bother to copy if it wouldn't change the file.\n    if $copy_on_change &&\n       old=`LC_ALL=C ls -dlL \"$dst\"\t2>/dev/null` &&\n       new=`LC_ALL=C ls -dlL \"$dsttmp\"\t2>/dev/null` &&\n\n       eval \"$initialize_posix_glob\" &&\n       $posix_glob set -f &&\n       set X $old && old=:$2:$4:$5:$6 &&\n       set X $new && new=:$2:$4:$5:$6 &&\n       $posix_glob set +f &&\n\n       test \"$old\" = \"$new\" &&\n       $cmpprog \"$dst\" \"$dsttmp\" >/dev/null 2>&1\n    then\n      rm -f \"$dsttmp\"\n    else\n      # Rename the file to the real destination.\n      $doit $mvcmd -f \"$dsttmp\" \"$dst\" 2>/dev/null ||\n\n      # The rename failed, perhaps because mv can't rename something else\n      # to itself, or perhaps because mv is so ancient that it does not\n      # support -f.\n      {\n\t# Now remove or move aside any old file at destination location.\n\t# We try this two ways since rm can't unlink itself on some\n\t# systems and the destination file might be busy for other\n\t# reasons.  In this case, the final cleanup might fail but the new\n\t# file should still install successfully.\n\t{\n\t  test ! -f \"$dst\" ||\n\t  $doit $rmcmd -f \"$dst\" 2>/dev/null ||\n\t  { $doit $mvcmd -f \"$dst\" \"$rmtmp\" 2>/dev/null &&\n\t    { $doit $rmcmd -f \"$rmtmp\" 2>/dev/null; :; }\n\t  } ||\n\t  { echo \"$0: cannot unlink or rename $dst\" >&2\n\t    (exit 1); exit 1\n\t  }\n\t} &&\n\n\t# Now rename the file to the real destination.\n\t$doit $mvcmd \"$dsttmp\" \"$dst\"\n      }\n    fi || exit 1\n\n    trap '' 0\n  fi\ndone\n\n# Local variables:\n# eval: (add-hook 'write-file-hooks 'time-stamp)\n# time-stamp-start: \"scriptversion=\"\n# time-stamp-format: \"%:y-%02m-%02d.%02H\"\n# time-stamp-time-zone: \"UTC\"\n# time-stamp-end: \"; # UTC\"\n# End:\n"
  },
  {
    "path": "lout/Makefile",
    "content": "include ../Makefile.options\n\nall: liblout.a\n\nliblout.a: container.o identity.o misc.o object.o signal.o unicode.o\n\t$(AR) $(ARFLAGS) liblout.a container.o identity.o misc.o object.o signal.o unicode.o\n\t$(RANLIB) liblout.a\n\ncontainer.o: container.cc container.hh\n\t$(CXXCOMPILE) -c container.cc\n\nidentity.o: identity.cc identity.hh\n\t$(CXXCOMPILE) -c identity.cc\n\nmisc.o: misc.cc misc.hh\n\t$(CXXCOMPILE) -c misc.cc\n\nobject.o: object.cc object.hh\n\t$(CXXCOMPILE) -c object.cc\n\nsignal.o: signal.cc signal.hh\n\t$(CXXCOMPILE) -c signal.cc\n\nunicode.o: unicode.cc unicode.hh\n\t$(CXXCOMPILE) -c unicode.cc\n\nclean:\n\trm -f *.o *.a\n\ninstall:\nuninstall:\n"
  },
  {
    "path": "lout/container.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#define PRINTF(fmt, ...)\n//#define PRINTF printf\n\n#include \"container.hh\"\n#include \"misc.hh\"\n#include \"debug.hh\"\n\nnamespace lout {\n\nusing namespace object;\n\nnamespace container {\n\nnamespace untyped {\n\n// -------------\n//    Iterator\n// -------------\n\nIterator::Iterator()\n{\n   impl = NULL;\n}\n\nIterator::Iterator(const Iterator &it2)\n{\n   impl = it2.impl;\n   if (impl)\n      impl->ref();\n}\n\nIterator::Iterator(Iterator &it2)\n{\n   impl = it2.impl;\n   if (impl)\n      impl->ref();\n}\n\nIterator &Iterator::operator=(const Iterator &it2)\n{\n   if (impl)\n      impl->unref();\n   impl = it2.impl;\n   if (impl)\n      impl->ref();\n   return *this;\n}\n\nIterator &Iterator::operator=(Iterator &it2)\n{\n   if (impl)\n      impl->unref();\n   impl = it2.impl;\n   if (impl)\n      impl->ref();\n   return *this;\n}\n\nIterator::~Iterator()\n{\n   if (impl)\n      impl->unref();\n}\n\n// ----------------\n//    Collection\n// ----------------\n\nvoid Collection::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append(\"{ \");\n   bool first = true;\n   for (Iterator it = iterator(); it.hasNext(); ) {\n      if (!first)\n         sb->append(\", \");\n      it.getNext()->intoStringBuffer(sb);\n      first = false;\n   }\n   sb->append(\" }\");\n}\n\n// ------------\n//    Vector\n// ------------\n\nVector::Vector(int initSize, bool ownerOfObjects)\n{\n   DBG_OBJ_CREATE (\"lout::container::untyped::Vector\");\n\n   numAlloc = initSize == 0 ? 1 : initSize;\n   this->ownerOfObjects = ownerOfObjects;\n   numElements = 0;\n   array = (Object**)malloc(numAlloc * sizeof(Object*));\n}\n\nVector::~Vector()\n{\n   clear();\n   free(array);\n\n   DBG_OBJ_DELETE ();\n}\n\nint Vector::size ()\n{\n   return numElements;\n}\n\nvoid Vector::put(Object *newElement, int newPos)\n{\n   if (newPos == -1)\n      newPos = numElements;\n\n   // Old entry is overwritten.\n   if (newPos < numElements) {\n      if (ownerOfObjects && array[newPos])\n         delete array[newPos];\n   }\n\n   // Allocated memory has to be increased.\n   if (newPos >= numAlloc) {\n      while (newPos >= numAlloc)\n         numAlloc *= 2;\n      void *vp;\n      assert((vp = realloc(array, numAlloc * sizeof(Object*))));\n      if (!vp) exit(-2); // when NDEBUG is defined\n      array = (Object**)vp;\n   }\n\n   // Insert NULL's into possible gap before new position.\n   for (int i = numElements; i < newPos; i++)\n      array[i] = NULL;\n\n   if (newPos >= numElements)\n      numElements = newPos + 1;\n\n   array[newPos] = newElement;\n}\n\nvoid Vector::clear()\n{\n   if (ownerOfObjects) {\n      for (int i = 0; i < numElements; i++)\n         if (array[i])\n            delete array[i];\n   }\n\n   numElements = 0;\n}\n\nvoid Vector::insert(Object *newElement, int pos)\n{\n   if (pos >= numElements)\n      put(newElement, pos);\n   else {\n      numElements++;\n\n      if (numElements >= numAlloc) {\n         // Allocated memory has to be increased.\n         numAlloc *= 2;\n         void *vp;\n         assert((vp = realloc(array, numAlloc * sizeof(Object*))));\n         if (!vp) exit(-2); // when NDEBUG is defined\n         array = (Object**)vp;\n      }\n\n      for (int i = numElements - 1; i > pos; i--)\n         array[i] = array[i - 1];\n\n      array[pos] = newElement;\n   }\n}\n\nvoid Vector::remove(int pos)\n{\n   if (ownerOfObjects && array[pos])\n      delete array[pos];\n\n   for (int i = pos + 1; i < numElements; i++)\n      array[i - 1] = array[i];\n\n   numElements--;\n}\n\n/**\n * Sort the elements in the vector. Assumes that all elements are Comparable's.\n */\nvoid Vector::sort(Comparator *comparator)\n{\n   Comparator::compareFunComparator = comparator;\n   qsort (array, numElements, sizeof(Object*), Comparator::compareFun);\n}\n\n/**\n * \\brief Use binary search to find an element in a sorted vector.\n *\n * If \"mustExist\" is true, only exact matches are found; otherwise, -1\n * is returned. If it is false, the position of the next greater\n * element is returned, or, if the key is the greatest element, the\n * size of the array. (This is the value which can be used for\n * insertion; see insertSortet()).\n */\nint Vector::bsearch(Object *key, bool mustExist, int start, int end,\n                    Comparator *comparator)\n{\n   // The case !mustExist is not handled by bsearch(3), so here is a\n   // new implementation.\n\n   DBG_OBJ_MSGF (\"container\", 0,\n                 \"<b>bsearch</b> (<i>key</i>, %s, %d, %d, <i>comparator</i>) \"\n                 \"[size is %d]\",\n                 mustExist ? \"true\" : \"false\", start, end, size ());\n   DBG_OBJ_MSG_START ();\n\n   int result = -123; // Compiler happiness: GCC 4.7 does not handle this?\n\n   if (start > end) {\n      DBG_OBJ_MSG (\"container\", 1, \"empty\");\n      result = mustExist ? -1 : start;\n   } else {\n      int low = start, high = end;\n      bool found = false;\n\n      while (!found) {\n         int index = (low + high) / 2;\n         int c = comparator->compare (key, array[index]);\n         DBG_OBJ_MSGF (\"container\", 1,\n                       \"searching within %d and %d; compare key with #%d => %d\",\n                       low, high, index, c);\n         if (c == 0) {\n            found = true;\n            result = index;\n         } else {\n            if (low >= high) {\n               if (mustExist) {\n                  found = true;\n                  result = -1;\n               } else {\n                  found = true;\n                  result = c > 0 ? index + 1 : index;\n               }\n            }\n\n            if (c < 0)\n               high = index - 1;\n            else\n               low = index + 1;\n         }\n      }\n   }\n\n   DBG_OBJ_MSGF (\"container\", 1, \"result = %d\", result);\n   DBG_OBJ_MSG_END ();\n   return result;\n}\n\nObject *Vector::VectorIterator::getNext()\n{\n   if (index < vector->numElements - 1)\n      index++;\n\n   return index < vector->numElements ? vector->array[index] : NULL;\n}\n\nbool Vector::VectorIterator::hasNext()\n{\n   return index < vector->numElements - 1;\n}\n\nCollection0::AbstractIterator* Vector::createIterator()\n{\n   return new VectorIterator(this);\n}\n\n// ------------\n//    List\n// ------------\n\nList::List(bool ownerOfObjects)\n{\n   this->ownerOfObjects = ownerOfObjects;\n   first = last = NULL;\n   numElements = 0;\n}\n\nList::~List()\n{\n   clear();\n}\n\nint List::size ()\n{\n   return numElements;\n}\n\nbool List::equals(Object *other)\n{\n   List *otherList = (List*)other;\n   Node *node1 = first, *node2 = otherList->first;\n   while (node1 != NULL && node2 != NULL ) {\n      if (!node1->object->equals (node2->object))\n         return false;\n      node1 = node1->next;\n      node2 = node2->next;\n   }\n   return node1 == NULL && node2 == NULL;\n}\n\nint List::hashValue()\n{\n   int h = 0;\n   for (Node *node = first; node; node = node->next)\n      h = h ^ node->object->hashValue ();\n   return h;\n}\n\nvoid List::clear()\n{\n   while (first) {\n      if (ownerOfObjects && first->object)\n         delete first->object;\n      Node *next = first->next;\n      delete first;\n      first = next;\n   }\n\n   last = NULL;\n   numElements = 0;\n}\n\nvoid List::append(Object *element)\n{\n   Node *newLast = new Node;\n   newLast->next = NULL;\n   newLast->object = element;\n\n   if (last) {\n      last->next = newLast;\n      last = newLast;\n   } else\n      first = last = newLast;\n\n   numElements++;\n}\n\nbool List::insertBefore(object::Object *beforeThis, object::Object *neew)\n{\n   Node *beforeCur, *cur;\n\n   for (beforeCur = NULL, cur = first; cur; beforeCur = cur, cur = cur->next) {\n      if (cur->object == beforeThis) {\n         Node *newNode = new Node;\n         newNode->next = cur;\n         newNode->object = neew;\n         \n         if (beforeCur)\n            beforeCur->next = newNode;\n         else\n            first = newNode;\n\n         numElements++;\n         return true;\n      }\n   }\n\n   return false;\n}\n\nbool List::remove0(Object *element, bool compare, bool doNotDeleteAtAll)\n{\n   Node *beforeCur, *cur;\n\n   for (beforeCur = NULL, cur = first; cur; beforeCur = cur, cur = cur->next) {\n      if (compare ?\n          (cur->object && element->equals(cur->object)) :\n          element == cur->object) {\n         if (beforeCur) {\n            beforeCur->next = cur->next;\n            if (cur->next == NULL)\n               last = beforeCur;\n         } else {\n            first = cur->next;\n            if (first == NULL)\n               last = NULL;\n         }\n\n         if (ownerOfObjects && cur->object && !doNotDeleteAtAll)\n            delete cur->object;\n         delete cur;\n\n         numElements--;\n         return true;\n      }\n   }\n\n   return false;\n}\n\nObject *List::ListIterator::getNext()\n{\n   Object *object;\n\n   if (current) {\n      object = current->object;\n      current = current->next;\n   } else\n      object = NULL;\n\n   return object;\n}\n\nbool List::ListIterator::hasNext()\n{\n   return current != NULL;\n}\n\nCollection0::AbstractIterator* List::createIterator()\n{\n   return new ListIterator(first);\n}\n\n\n// ---------------\n//    HashSet\n// ---------------\n\nHashSet::HashSet(bool ownerOfObjects, int tableSize)\n{\n   this->ownerOfObjects = ownerOfObjects;\n   this->tableSize = tableSize;\n\n   table = new Node*[tableSize];\n   for (int i = 0; i < tableSize; i++)\n      table[i] = NULL;\n\n   numElements = 0;\n}\n\nHashSet::~HashSet()\n{\n   for (int i = 0; i < tableSize; i++) {\n      Node *n1 = table[i];\n      while (n1) {\n         Node *n2 = n1->next;\n\n         // It seems appropriate to call \"clearNode(n1)\" here instead\n         // of \"delete n1->object\", but since this is the destructor,\n         // the implementation of a sub class would not be called\n         // anymore. This is the reason why HashTable has an\n         // destructor.\n         if (ownerOfObjects) {\n            PRINTF (\"- deleting object: %s\\n\", n1->object->toString());\n            delete n1->object;\n         }\n\n         delete n1;\n         n1 = n2;\n      }\n   }\n\n   delete[] table;\n}\n\nint HashSet::size ()\n{\n   return numElements;\n}\n\nHashSet::Node *HashSet::createNode()\n{\n   return new Node;\n}\n\nvoid HashSet::clearNode(HashSet::Node *node)\n{\n   if (ownerOfObjects) {\n      PRINTF (\"- deleting object: %s\\n\", node->object->toString());\n      delete node->object;\n   }\n}\n\nHashSet::Node *HashSet::findNode(Object *object) const\n{\n   int h = calcHashValue(object);\n   for (Node *node = table[h]; node; node = node->next) {\n      if (object->equals(node->object))\n         return node;\n   }\n\n   return NULL;\n}\n\nHashSet::Node *HashSet::insertNode(Object *object)\n{\n   // Look whether object is already contained.\n   Node *node = findNode(object);\n   if (node) {\n      clearNode(node);\n      numElements--;\n   } else {\n      int h = calcHashValue(object);\n      node = createNode ();\n      node->next = table[h];\n      table[h] = node;\n      numElements++;\n   }\n\n   node->object = object;\n   return node;\n}\n\n\nvoid HashSet::put(Object *object)\n{\n   insertNode (object);\n}\n\nbool HashSet::contains(Object *object) const\n{\n   int h = calcHashValue(object);\n   for (Node *n = table[h]; n; n = n->next) {\n      if (object->equals(n->object))\n         return true;\n   }\n\n   return false;\n}\n\nbool HashSet::remove(Object *object)\n{\n   int h = calcHashValue(object);\n   Node *last, *cur;\n\n   for (last = NULL, cur = table[h]; cur; last = cur, cur = cur->next) {\n      if (object->equals(cur->object)) {\n         if (last)\n            last->next = cur->next;\n         else\n            table[h] = cur->next;\n\n         clearNode (cur);\n         delete cur;\n         numElements--;\n\n         return true;\n      }\n   }\n\n   return false;\n\n   // TODO for HashTable: also remove value.\n}\n\n// For historical reasons: this method once existed under the name\n// \"getKey\" in HashTable. It could be used to get the real object from\n// the table, but it was dangerous, because a change of this object\n// would also change the hash value, and so confuse the table.\n\n/*Object *HashSet::getReference (Object *object)\n{\n   int h = calcHashValue(object);\n   for (Node *n = table[h]; n; n = n->next) {\n      if (object->equals(n->object))\n         return n->object;\n   }\n\n   return NULL;\n}*/\n\nHashSet::HashSetIterator::HashSetIterator(HashSet *set)\n{\n   this->set = set;\n   node = NULL;\n   pos = -1;\n   gotoNext();\n}\n\nvoid HashSet::HashSetIterator::gotoNext()\n{\n   if (node)\n      node = node->next;\n\n   while (node == NULL) {\n      if (pos >= set->tableSize - 1)\n         return;\n      pos++;\n      node = set->table[pos];\n   }\n}\n\n\nObject *HashSet::HashSetIterator::getNext()\n{\n   Object *result;\n   if (node)\n      result = node->object;\n   else\n      result = NULL;\n\n   gotoNext();\n   return result;\n}\n\nbool HashSet::HashSetIterator::hasNext()\n{\n   return node != NULL;\n}\n\nCollection0::AbstractIterator* HashSet::createIterator()\n{\n   return new HashSetIterator(this);\n}\n\n// ---------------\n//    HashTable\n// ---------------\n\nHashTable::HashTable(bool ownerOfKeys, bool ownerOfValues, int tableSize) :\n   HashSet (ownerOfKeys, tableSize)\n{\n   this->ownerOfValues = ownerOfValues;\n}\n\nHashTable::~HashTable()\n{\n   // See comment in the destructor of HashSet.\n   for (int i = 0; i < tableSize; i++) {\n      for (Node *n = table[i]; n; n = n->next) {\n         if (ownerOfValues) {\n            Object *value = ((KeyValuePair*)n)->value;\n            if (value) {\n               PRINTF (\"- deleting value: %s\\n\", value->toString());\n               delete value;\n            }\n         }\n      }\n   }\n}\n\nHashSet::Node *HashTable::createNode()\n{\n   return new KeyValuePair;\n}\n\nvoid HashTable::clearNode(HashSet::Node *node)\n{\n   HashSet::clearNode (node);\n   if (ownerOfValues) {\n      Object *value = ((KeyValuePair*)node)->value;\n      if (value) {\n         PRINTF (\"- deleting value: %s\\n\", value->toString());\n         delete value;\n      }\n   }\n}\n\nvoid HashTable::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append(\"{ \");\n\n   bool first = true;\n   for (int i = 0; i < tableSize; i++) {\n      for (Node *node = table[i]; node; node = node->next) {\n         if (!first)\n            sb->append(\", \");\n         node->object->intoStringBuffer(sb);\n\n         sb->append(\" => \");\n\n         Object *value = ((KeyValuePair*)node)->value;\n         if (value)\n             value->intoStringBuffer(sb);\n         else\n            sb->append (\"null\");\n\n         first = false;\n      }\n   }\n\n   sb->append(\" }\");\n}\n\nvoid HashTable::put(Object *key, Object *value)\n{\n   KeyValuePair *node = (KeyValuePair*)insertNode(key);\n   node->value = value;\n}\n\nObject *HashTable::get(Object *key) const\n{\n   Node *node = findNode(key);\n   if (node)\n      return ((KeyValuePair*)node)->value;\n   else\n      return NULL;\n}\n\n// -----------\n//    Stack\n// -----------\n\nStack::Stack (bool ownerOfObjects)\n{\n   this->ownerOfObjects = ownerOfObjects;\n   bottom = top = NULL;\n   numElements = 0;\n}\n\nStack::~Stack()\n{\n   while (top)\n      pop ();\n}\n\nint Stack::size ()\n{\n   return numElements;\n}\n\nvoid Stack::push (object::Object *object)\n{\n   Node *newTop = new Node ();\n   newTop->object = object;\n   newTop->prev = top;\n\n   top = newTop;\n   if (bottom == NULL)\n      bottom = top;\n\n   numElements++;\n}\n\nvoid Stack::pushUnder (object::Object *object)\n{\n   Node *newBottom = new Node ();\n   newBottom->object = object;\n   newBottom->prev = NULL;\n   if (bottom != NULL)\n      bottom->prev = newBottom;\n\n   bottom = newBottom;\n   if (top == NULL)\n      top = bottom;\n\n   numElements++;\n}\n\nvoid Stack::pop ()\n{\n   Node *newTop = top->prev;\n\n   if (ownerOfObjects)\n      delete top->object;\n   delete top;\n\n   top = newTop;\n   if (top == NULL)\n      bottom = NULL;\n\n   numElements--;\n}\n\nObject *Stack::StackIterator::getNext()\n{\n   Object *object;\n\n   if (current) {\n      object = current->object;\n      current = current->prev;\n   } else\n      object = NULL;\n\n   return object;\n}\n\nbool Stack::StackIterator::hasNext()\n{\n   return current != NULL;\n}\n\nCollection0::AbstractIterator* Stack::createIterator()\n{\n   return new StackIterator(top);\n}\n\n} // namespace untyped\n\n} // namespace container\n\n} // namespace lout\n"
  },
  {
    "path": "lout/container.hh",
    "content": "#ifndef __LOUT_CONTAINER_HH_\n#define __LOUT_CONTAINER_HH_\n\n#include \"object.hh\"\n\nnamespace lout {\n\n/**\n * \\brief This namespace contains a framework for container classes, which\n *    members are instances of object::Object.\n *\n * A common problem in languages without garbage collection is, where the\n * children belong to, and so, who is responsible to delete them (instantiation\n * is always done by the caller). This information is here told to the\n * collections, each container has a constructor with the parameter\n * \"ownerOfObjects\" (HashTable has two such parameters, for keys and values).\n *\n * \\sa container::untyped, container::typed\n */\nnamespace container {\n\n/**\n * \\brief The container classes defined here contain instances of\n *    object::Object.\n *\n * Different sub-classes may be mixed, and you have to care about casting,\n * there is no type-safety.\n */\nnamespace untyped {\n\n/**\n * \\brief ...\n */\nclass Collection0: public object::Object\n{\n   friend class Iterator;\n\nprotected:\n   /**\n    * \\brief The base class for all iterators, as created by\n    *   container::untyped::Collection::createIterator.\n    */\n   class AbstractIterator: public object::Object\n   {\n   private:\n      int refcount;\n\n   public:\n      AbstractIterator() { refcount = 1; }\n\n      void ref () { refcount++; }\n      void unref () { refcount--; if (refcount == 0) delete this; }\n\n      virtual bool hasNext () = 0;\n      virtual Object *getNext () = 0;\n   };\n\nprotected:\n   virtual AbstractIterator* createIterator() = 0;\n};\n\n/**\n * \\brief This is a small wrapper for AbstractIterator, which may be used\n *    directly, not as a pointer, to makes memory management simpler.\n */\nclass Iterator\n{\n   friend class Collection;\n\nprivate:\n   Collection0::AbstractIterator *impl;\n\n   // Should not instantiated directly.\n   inline Iterator(Collection0::AbstractIterator *impl) { this->impl = impl; }\n\npublic:\n   Iterator();\n   Iterator(const Iterator &it2);\n   Iterator(Iterator &it2);\n   ~Iterator();\n   Iterator &operator=(const Iterator &it2);\n   Iterator &operator=(Iterator &it2);\n\n   inline bool hasNext() { return impl->hasNext(); }\n   inline object::Object *getNext() { return impl->getNext(); }\n};\n\n/**\n * \\brief Abstract base class for all container objects in container::untyped.\n */\nclass Collection: public Collection0\n{\npublic:\n   void intoStringBuffer(misc::StringBuffer *sb);\n   inline Iterator iterator() { Iterator it(createIterator()); return it; }\n   virtual int size() = 0;\n};\n\n\n/**\n * \\brief Container, which is implemented by an array, which is\n *    dynamically resized.\n */\nclass Vector: public Collection\n{\n   friend class VectorIterator;\n\nprivate:\n   object::Object **array;\n   int numAlloc, numElements;\n   bool ownerOfObjects;\n\n   class VectorIterator: public AbstractIterator\n   {\n   private:\n      Vector *vector;\n      int index;\n\n   public:\n      VectorIterator(Vector *vector) { this->vector = vector; index = -1; }\n      bool hasNext();\n      Object *getNext();\n   };\n\nprotected:\n   AbstractIterator* createIterator();\n\npublic:\n   Vector(int initSize, bool ownerOfObjects);\n   ~Vector();\n\n   int size();\n\n   void put(object::Object *newElement, int newPos = -1);\n   void insert(object::Object *newElement, int pos);\n\n   /**\n    * \\brief Insert into an already sorted vector.\n    *\n    * Notice that insertion is not very efficient, unless the position\n    * is rather at the end.\n    */\n   inline int insertSorted(object::Object *newElement,\n                           object::Comparator *comparator =\n                           &object::standardComparator)\n   { int pos = bsearch (newElement, false, comparator);\n      insert (newElement, pos); return pos; }\n\n   void remove(int pos);\n   inline object::Object *get(int pos) const\n   { return (pos >= 0 && pos < numElements) ? array[pos] : NULL; }\n   void clear();\n   void sort(object::Comparator *comparator = &object::standardComparator);\n   int bsearch(Object *key, bool mustExist, int start, int end,\n               object::Comparator *comparator = &object::standardComparator);\n   inline int bsearch(Object *key, bool mustExist,\n                      object::Comparator *comparator =\n                      &object::standardComparator)\n   { return bsearch (key, mustExist, 0, size () - 1, comparator); }\n};\n\n\n/**\n * \\brief A single-linked list.\n */\nclass List: public Collection\n{\n   friend class ListIterator;\n\nprivate:\n   struct Node\n   {\n   public:\n      object::Object *object;\n      Node *next;\n   };\n\n   class ListIterator: public AbstractIterator\n   {\n   private:\n      List::Node *current;\n   public:\n      ListIterator(List::Node *node) { current = node; }\n      bool hasNext();\n      Object *getNext();\n   };\n\n   Node *first, *last;\n   bool ownerOfObjects;\n   int numElements;\n\n   bool remove0(object::Object *element, bool compare, bool doNotDeleteAtAll);\n\nprotected:\n   AbstractIterator* createIterator();\n\npublic:\n   List(bool ownerOfObjects);\n   ~List();\n\n   bool equals(Object *other);\n   int hashValue();\n\n   int size ();\n\n   void clear();\n   void append(object::Object *element);\n   bool insertBefore(object::Object *beforeThis, object::Object *neew);\n   inline bool removeRef(object::Object *element)\n   { return remove0(element, false, false); }\n   inline bool remove(object::Object *element)\n   { return remove0(element, true, false); }\n   inline bool detachRef(object::Object *element)\n   { return remove0(element, false, true); }\n   inline int size() const { return numElements; }\n   inline bool isEmpty() const { return numElements == 0; }\n   inline object::Object *getFirst() const { return first->object; }\n   inline object::Object *getLast() const { return last->object; }\n};\n\n\n/**\n * \\brief A hash set.\n */\nclass HashSet: public Collection\n{\n   friend class HashSetIterator;\n\nprotected:\n   struct Node\n   {\n      object::Object *object;\n      Node *next;\n   };\n\n   Node **table;\n   int tableSize, numElements;\n   bool ownerOfObjects;\n\n   inline int calcHashValue(object::Object *object) const\n   {\n      return abs(object->hashValue()) % tableSize;\n   }\n\n   virtual Node *createNode();\n   virtual void clearNode(Node *node);\n\n   Node *findNode(object::Object *object) const;\n   Node *insertNode(object::Object *object);\n\n   AbstractIterator* createIterator();\n\nprivate:\n   class HashSetIterator: public Collection0::AbstractIterator\n   {\n   private:\n      HashSet *set;\n      HashSet::Node *node;\n      int pos;\n\n      void gotoNext();\n\n   public:\n      HashSetIterator(HashSet *set);\n      bool hasNext();\n      Object *getNext();\n   };\n\npublic:\n   HashSet(bool ownerOfObjects, int tableSize = 251);\n   ~HashSet();\n\n   int size ();\n\n   void put (object::Object *object);\n   bool contains (object::Object *key) const;\n   bool remove (object::Object *key);\n   //Object *getReference (object::Object *object);\n};\n\n/**\n * \\brief A hash table.\n */\nclass HashTable: public HashSet\n{\nprivate:\n   bool ownerOfValues;\n\n   struct KeyValuePair: public Node\n   {\n      object::Object *value;\n   };\n\nprotected:\n   Node *createNode();\n   void clearNode(Node *node);\n\npublic:\n   HashTable(bool ownerOfKeys, bool ownerOfValues, int tableSize = 251);\n   ~HashTable();\n\n   void intoStringBuffer(misc::StringBuffer *sb);\n\n   void put (object::Object *key, object::Object *value);\n   object::Object *get (object::Object *key) const;\n};\n\n/**\n * \\brief A stack (LIFO). Can be used as Queue (FIFO) when pushUnder()\n *     is used instead of push().\n *\n * Note that the iterator returns all elements in the reversed order they have\n * been put on the stack.\n */\nclass Stack: public Collection\n{\n   friend class StackIterator;\n\nprivate:\n   class Node\n   {\n   public:\n      object::Object *object;\n      Node *prev;\n   };\n\n   class StackIterator: public AbstractIterator\n   {\n   private:\n      Stack::Node *current;\n   public:\n      StackIterator(Stack::Node *node) { current = node; }\n      bool hasNext();\n      Object *getNext();\n   };\n\n   Node *bottom, *top;\n   bool ownerOfObjects;\n   int numElements;\n\nprotected:\n   AbstractIterator* createIterator();\n\npublic:\n   Stack (bool ownerOfObjects);\n   ~Stack();\n\n   int size ();\n\n   void push (object::Object *object);\n   void pushUnder (object::Object *object);\n   inline object::Object *getTop () const { return top ? top->object : NULL; }\n   void pop ();\n   inline int size() const { return numElements; }\n};\n\n} // namespace untyped\n\n/**\n * \\brief This namespace provides thin wrappers, implemented as C++ templates,\n *    to gain type-safety.\n *\n * Notice, that nevertheless, all objects still have to be instances of\n * object::Object.\n */\nnamespace typed {\n\ntemplate <class T> class Collection;\n\n/**\n * \\brief Typed version of container::untyped::Iterator.\n */\ntemplate <class T> class Iterator\n{\n   friend class Collection<T>;\n\nprivate:\n   untyped::Iterator base;\n\npublic:\n   inline Iterator() { }\n   inline Iterator(const Iterator<T> &it2) { this->base = it2.base; }\n   inline Iterator(Iterator<T> &it2) { this->base = it2.base; }\n   ~Iterator() { }\n   inline Iterator &operator=(const Iterator<T> &it2)\n   { this->base = it2.base; return *this; }\n   inline Iterator &operator=(Iterator<T> &it2)\n   { this->base = it2.base; return *this; }\n\n   inline bool hasNext() { return this->base.hasNext(); }\n   inline T *getNext() { return (T*)this->base.getNext(); }\n};\n\n/**\n * \\brief Abstract base class template for all container objects in\n *    container::typed.\n *\n * Actually, a wrapper for container::untyped::Collection.\n */\ntemplate <class T> class Collection: public object::Object\n{\nprotected:\n   untyped::Collection *base;\n\npublic:\n   Collection () { this->base = NULL; }\n   ~Collection () { if (this->base) delete this->base; }\n\n   bool equals(Object *other)\n   { return this->base->equals (((Collection<T>*)other)->base); }\n\n   int hashValue() { return this->base->hashValue (); }\n\n   void intoStringBuffer(misc::StringBuffer *sb)\n   { this->base->intoStringBuffer(sb); }\n   inline Iterator<T> iterator() {\n      Iterator<T> it; it.base = this->base->iterator(); return it; }\n   inline int size() { return this->base->size (); }\n};\n\n\n/**\n * \\brief Typed version of container::untyped::Vector.\n */\ntemplate <class T> class Vector: public Collection <T>\n{\npublic:\n   inline Vector(int initSize, bool ownerOfObjects) {\n      this->base = new untyped::Vector(initSize, ownerOfObjects); }\n\n   inline void put(T *newElement, int newPos = -1)\n   { ((untyped::Vector*)this->base)->put(newElement, newPos); }\n   inline void insert(T *newElement, int pos)\n   { ((untyped::Vector*)this->base)->insert(newElement, pos); }\n   inline int insertSorted(T *newElement,\n                           object::Comparator *comparator =\n                           &object::standardComparator)\n   { return ((untyped::Vector*)this->base)->insertSorted(newElement,\n                                                         comparator); }\n   inline void remove(int pos) { ((untyped::Vector*)this->base)->remove(pos); }\n   inline T *get(int pos) const\n   { return (T*)((untyped::Vector*)this->base)->get(pos); }\n   inline void clear() { ((untyped::Vector*)this->base)->clear(); }\n   inline void sort(object::Comparator *comparator =\n                    &object::standardComparator)\n   { ((untyped::Vector*)this->base)->sort(comparator); }\n   inline int bsearch(T *key, bool mustExist, int start, int end,\n                      object::Comparator *comparator =\n                      &object::standardComparator)\n   { return ((untyped::Vector*)this->base)->bsearch(key, mustExist, start, end,\n                                                    comparator); }\n   inline int bsearch(T *key, bool mustExist,\n                      object::Comparator *comparator =\n                      &object::standardComparator)\n   { return ((untyped::Vector*)this->base)->bsearch(key, mustExist,\n                                                    comparator); }\n};\n\n\n/**\n * \\brief Typed version of container::untyped::List.\n */\ntemplate <class T> class List: public Collection <T>\n{\npublic:\n   inline List(bool ownerOfObjects)\n   { this->base = new untyped::List(ownerOfObjects); }\n\n   inline void clear() { ((untyped::List*)this->base)->clear(); }\n   inline void append(T *element)\n   { ((untyped::List*)this->base)->append(element); }\n   inline bool insertBefore(object::Object *beforeThis, object::Object *neew)\n   { return ((untyped::List*)this->base)->insertBefore(beforeThis, neew); }\n   inline bool removeRef(T *element) {\n      return ((untyped::List*)this->base)->removeRef(element); }\n   inline bool remove(T *element) {\n      return ((untyped::List*)this->base)->remove(element); }\n   inline bool detachRef(T *element) {\n      return ((untyped::List*)this->base)->detachRef(element); }\n\n   inline bool isEmpty() const\n   { return ((untyped::List*)this->base)->isEmpty(); }\n   inline T *getFirst() const\n   { return (T*)((untyped::List*)this->base)->getFirst(); }\n   inline T *getLast() const\n   { return (T*)((untyped::List*)this->base)->getLast(); }\n};\n\n/**\n * \\brief Typed version of container::untyped::HashSet.\n */\ntemplate <class T> class HashSet: public Collection <T>\n{\nprotected:\n   inline HashSet() { }\n\npublic:\n   inline HashSet(bool owner, int tableSize = 251)\n   { this->base = new untyped::HashSet(owner, tableSize); }\n\n   inline void put(T *object)\n   { return ((untyped::HashSet*)this->base)->put(object); }\n   inline bool contains(T *object) const\n   { return ((untyped::HashSet*)this->base)->contains(object); }\n   inline bool remove(T *object)\n   { return ((untyped::HashSet*)this->base)->remove(object); }\n   //inline T *getReference(T *object)\n   //{ return (T*)((untyped::HashSet*)this->base)->getReference(object); }\n};\n\n/**\n * \\brief Typed version of container::untyped::HashTable.\n */\ntemplate <class K, class V> class HashTable: public HashSet <K>\n{\npublic:\n   inline HashTable(bool ownerOfKeys, bool ownerOfValues, int tableSize = 251)\n   { this->base = new untyped::HashTable(ownerOfKeys, ownerOfValues,\n                                         tableSize); }\n\n   inline void put(K *key, V *value)\n   { return ((untyped::HashTable*)this->base)->put(key, value); }\n   inline V *get(K *key) const\n   { return (V*)((untyped::HashTable*)this->base)->get(key); }\n};\n\n/**\n * \\brief Typed version of container::untyped::Stack.\n */\ntemplate <class T> class Stack: public Collection <T>\n{\npublic:\n   inline Stack (bool ownerOfObjects)\n   { this->base = new untyped::Stack (ownerOfObjects); }\n\n   inline void push (T *object) {\n      ((untyped::Stack*)this->base)->push (object); }\n   inline void pushUnder (T *object)\n   { ((untyped::Stack*)this->base)->pushUnder (object); }\n   inline T *getTop () const\n   { return (T*)((untyped::Stack*)this->base)->getTop (); }\n   inline void pop () { ((untyped::Stack*)this->base)->pop (); }\n};\n\n} // namespace untyped\n\n} // namespace container\n\n} // namespace lout\n\n#endif // __LOUT_CONTAINER_HH_\n"
  },
  {
    "path": "lout/debug.hh",
    "content": "#ifndef __LOUT_DEBUG_H__\n#define __LOUT_DEBUG_H__\n\n/*\n * Simple debug messages. Add:\n *\n *    #define DEBUG_LEVEL <n>\n *    #include \"debug.h\"\n *\n * to the file you are working on, or let DEBUG_LEVEL undefined to\n * disable all messages. A higher level denotes a greater importance\n * of the message.\n */\n\n#include <stdio.h>\n\n#define D_STMT_START      do\n#define D_STMT_END        while (0)\n\n#define D_STMT_NOP        D_STMT_START { } D_STMT_END\n\n# ifdef DEBUG_LEVEL\n#    define DEBUG_MSG(level, ...)                     \\\n        D_STMT_START {                                \\\n           if (DEBUG_LEVEL && (level) >= DEBUG_LEVEL) \\\n              printf(__VA_ARGS__);                    \\\n        } D_STMT_END\n# else\n#    define DEBUG_MSG(level, ...)\n# endif /* DEBUG_LEVEL */\n\n#include \"debug_rtfl.hh\"\n\n/* Some extensions for RTFL dealing with static stuff. */\n\n#ifdef DBG_RTFL\n\n#define DBG_OBJ_MSG_S(aspect, prio, msg)         \\\n   RTFL_OBJ_PRINT (\"msg\", \"s:s:d:s\", \"<static>\", aspect, prio, msg)\n\n#define DBG_OBJ_MSGF_S(aspect, prio, fmt, ...) \\\n   STMT_START { \\\n      char msg[256]; \\\n      snprintf (msg, sizeof (msg), fmt, __VA_ARGS__); \\\n      RTFL_OBJ_PRINT (\"msg\", \"s:s:d:s\", \"<static>\", aspect, prio, msg) \\\n   } STMT_END\n\n#define DBG_OBJ_ENTER0_S(aspect, prio, funname) \\\n   RTFL_OBJ_PRINT (\"enter\", \"s:s:d:s:\", \"<static>\", aspect, prio, funname);\n\n#define DBG_OBJ_ENTER_S(aspect, prio, funname, fmt, ...) \\\n   STMT_START { \\\n      char args[256]; \\\n      snprintf (args, sizeof (args), fmt, __VA_ARGS__); \\\n      RTFL_OBJ_PRINT (\"enter\", \"s:s:d:s:s\", \"<static>\", aspect, prio, funname, \\\n                      args); \\\n   } STMT_END\n\n#define DBG_OBJ_LEAVE_S() \\\n   RTFL_OBJ_PRINT (\"leave\", \"s\", \"<static>\");\n\n#define DBG_OBJ_LEAVE_VAL_S(fmt, ...) \\\n   STMT_START { \\\n      char vals[256]; \\\n      snprintf (vals, sizeof (vals), fmt, __VA_ARGS__); \\\n      RTFL_OBJ_PRINT (\"leave\", \"s:s\", \"<static>\", vals); \\\n   } STMT_END\n\n#else /* DBG_RTFL */\n\n#define STMT_NOP         do { } while (0)\n\n#define DBG_IF_RTFL if(0)\n\n#define DBG_OBJ_MSG_S(aspect, prio, msg)                  STMT_NOP\n#define DBG_OBJ_MSGF_S(aspect, prio, fmt, ...)            STMT_NOP\n#define DBG_OBJ_ENTER0_S(aspect, prio, funname)           STMT_NOP\n#define DBG_OBJ_ENTER_S(aspect, prio, funname, fmt, ...)  STMT_NOP\n#define DBG_OBJ_LEAVE_S()                                 STMT_NOP\n#define DBG_OBJ_LEAVE_VAL_S(fmt, ...)                     STMT_NOP\n\n#endif /* DBG_RTFL */\n\n#endif /* __LOUT_DEBUG_H__ */\n\n\n"
  },
  {
    "path": "lout/debug_rtfl.hh",
    "content": "// WARNING: This file has been generated. Do not edit!\n\n/*\n * This file is part of RTFL, see <http://home.gna.org/rtfl/>\n * for details.\n *\n * This file (but not RTFL itself) is in the public domain, since it is only a\n * simple implementation of a protocol, containing nothing more than trivial\n * work. However, it would be nice to keep this notice, along with the URL\n * above.\n *\n * ----------------------------------------------------------------------------\n *\n * Defines macros for printing RTFL commands. See documentation for detail\n * (online at <http://home.gna.org/rtfl/doc/rtfl.html>). These macros are only\n * active, when the pre-processor variable DBG_RTFL is defined. If not,\n * alternatives are defined, which have no effect.\n *\n * This variant assumes that __FILE__ is only the base of the source file name,\n * so, to get the full path, CUR_WORKING_DIR has to be defined. See RTFL\n * documentation for more details.\n */\n\n#ifndef __DEBUG_RTFL_HH__\n#define __DEBUG_RTFL_HH__\n\n#ifdef DBG_RTFL\n\n// =======================================\n//           Used by all modules\n// =======================================\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <unistd.h>\n#include <sys/time.h>\n\n#define DBG_IF_RTFL if(1)\n\n#define STMT_START       do\n#define STMT_END         while (0)\n\n// Prints an RTFL message to stdout. \"fmt\" contains simple format\n// characters how to deal with the additional arguments (no \"%\"\n// preceeding, as in printf) or \"q\" (which additionally\n// (double-)quotes quotation marks) or \"c\" (short for \"#%06x\" and used\n// for colors), or other characters, which are simply printed. No\n// quoting: this function cannot be used to print the characters \"d\",\n// \"p\", \"s\" and \"q\" directly.\n\ninline void rtfl_print (const char *module, const char *version,\n                        const char *file, int line, const char *fmt, ...)\n{\n   // \"\\n\" at the beginning just in case that the previous line is not\n   // finished yet.\n   printf (\"\\n[rtfl-%s-%s]%s:%d:%d:\", module, version, file, line, getpid ());\n\n   va_list args;\n   va_start (args, fmt);\n\n   for (int i = 0; fmt[i]; i++) {\n      int n;\n      void *p;\n      char *s;\n\n      switch (fmt[i]) {\n      case 'd':\n         n = va_arg(args, int);\n         printf (\"%d\", n);\n         break;\n\n      case 'p':\n         p = va_arg(args, void*);\n         printf (\"%p\", p);\n         break;\n\n      case 's':\n         s = va_arg (args, char*);\n         for (int j = 0; s[j]; j++) {\n            if (s[j] == ':' || s[j] == '\\\\')\n               putchar ('\\\\');\n            putchar (s[j]);\n         }\n         break;\n\n      case 'q':\n         s = va_arg (args, char*);\n         for (int j = 0; s[j]; j++) {\n            if (s[j] == ':' || s[j] == '\\\\')\n               putchar ('\\\\');\n            else if (s[j] == '\\\"')\n               printf (\"\\\\\\\\\"); // a quoted quoting character\n            putchar (s[j]);\n         }\n         break;\n\n      case 'c':\n         n = va_arg(args, int);\n         printf (\"#%06x\", n);\n         break;\n\n      default:\n         putchar (fmt[i]);\n         break;\n      }\n   }\n\n   va_end (args);\n\n   putchar ('\\n');\n   fflush (stdout);\n}\n\n#define RTFL_PRINT(module, version, cmd, fmt, ...) \\\n   rtfl_print (module, version, CUR_WORKING_DIR \"/\" __FILE__, __LINE__, \\\n               \"s:\" fmt, cmd, __VA_ARGS__)\n\n\n// ==================================\n//           General module\n// ==================================\n\n#define RTFL_GEN_VERSION \"1.0\"\n\n#define RTFL_GEN_PRINT(cmd, fmt, ...) \\\n   RTFL_PRINT (\"gen\", RTFL_GEN_VERSION, cmd, fmt, __VA_ARGS__)\n\n#define DBG_GEN_TIME() \\\n   STMT_START { \\\n      struct timeval tv; \\\n      gettimeofday(&tv, NULL); \\\n      char buf[32]; \\\n      snprintf (buf, sizeof (buf), \"%ld%06ld\", tv.tv_sec, tv.tv_usec); \\\n      RTFL_GEN_PRINT (\"time\", \"s\", buf); \\\n   } STMT_END\n\n\n// ==================================\n//           Objects module\n// ==================================\n\n#define RTFL_OBJ_VERSION \"1.0\"\n\n#define RTFL_OBJ_PRINT(cmd, fmt, ...) \\\n   RTFL_PRINT (\"obj\", RTFL_OBJ_VERSION, cmd, fmt, __VA_ARGS__)\n\n#define DBG_OBJ_MSG(aspect, prio, msg) \\\n   DBG_OBJ_MSG_O (aspect, prio, this, msg)\n\n#define DBG_OBJ_MSG_O(aspect, prio, obj, msg) \\\n   RTFL_OBJ_PRINT (\"msg\", \"p:s:d:s\", obj, aspect, prio, msg)\n\n#define DBG_OBJ_MSGF(aspect, prio, fmt, ...) \\\n   STMT_START { \\\n      char msg[256]; \\\n      snprintf (msg, sizeof (msg), fmt, __VA_ARGS__); \\\n      DBG_OBJ_MSG (aspect, prio, msg); \\\n   } STMT_END\n\n#define DBG_OBJ_MSGF_O(aspect, prio, obj, fmt, ...) \\\n   STMT_START { \\\n      char msg[256]; \\\n      snprintf (msg, sizeof (msg), fmt, __VA_ARGS__); \\\n      DBG_OBJ_MSG_O (aspect, prio, obj, msg); \\\n   } STMT_END\n\n#define DBG_OBJ_MARK(aspect, prio, mark) \\\n   DBG_OBJ_MARK_O (aspect, prio, this, mark)\n\n#define DBG_OBJ_MARK_O(aspect, prio, obj, mark) \\\n   RTFL_OBJ_PRINT (\"mark\", \"p:s:d:s\", obj, aspect, prio, mark)\n\n#define DBG_OBJ_MARKF(aspect, prio, fmt, ...) \\\n   STMT_START { \\\n      char mark[256]; \\\n      snprintf (mark, sizeof (mark), fmt, __VA_ARGS__); \\\n      DBG_OBJ_MARK (aspect, prio, mark); \\\n   } STMT_END\n\n#define DBG_OBJ_MARKF_O(aspect, prio, obj, fmt, ...) \\\n   STMT_START { \\\n      char mark[256]; \\\n      snprintf (mark, sizeof (mark), fmt, __VA_ARGS__); \\\n      DBG_OBJ_MARK_O (aspect, prio, obj, mark); \\\n   } STMT_END\n\n#define DBG_OBJ_MSG_START() \\\n   DBG_OBJ_MSG_START_O (this)\n\n#define DBG_OBJ_MSG_START_O(obj) \\\n   RTFL_OBJ_PRINT (\"msg-start\", \"p\", obj)\n\n#define DBG_OBJ_MSG_END() \\\n   DBG_OBJ_MSG_END_O (this)\n\n#define DBG_OBJ_MSG_END_O(obj) \\\n   RTFL_OBJ_PRINT (\"msg-end\", \"p\", obj)\n\n#define DBG_OBJ_ENTER0(aspect, prio, funname) \\\n   DBG_OBJ_ENTER0_O (aspect, prio, this, funname)\n\n#define DBG_OBJ_ENTER0_O(aspect, prio, obj, funname) \\\n   RTFL_OBJ_PRINT (\"enter\", \"p:s:d:s:\", obj, aspect, prio, funname);\n\n#define DBG_OBJ_ENTER(aspect, prio, funname, fmt, ...) \\\n   STMT_START { \\\n      char args[256]; \\\n      snprintf (args, sizeof (args), fmt, __VA_ARGS__); \\\n      RTFL_OBJ_PRINT (\"enter\", \"p:s:d:s:s\", this, aspect, prio, funname, \\\n                      args); \\\n   } STMT_END\n\n#define DBG_OBJ_ENTER_O(aspect, prio, obj, funname, fmt, ...) \\\n   STMT_START { \\\n      char args[256]; \\\n      snprintf (args, sizeof (args), fmt, __VA_ARGS__); \\\n      RTFL_OBJ_PRINT (\"enter\", \"p:s:d:s:s\", obj, aspect, prio, funname, \\\n                      args); \\\n   } STMT_END\n\n#define DBG_OBJ_LEAVE() \\\n   DBG_OBJ_LEAVE_O (this)\n\n#define DBG_OBJ_LEAVE_O(obj) \\\n   RTFL_OBJ_PRINT (\"leave\", \"p\", obj);\n\n#define DBG_OBJ_LEAVE_VAL(fmt, ...) \\\n   STMT_START { \\\n      char vals[256]; \\\n      snprintf (vals, sizeof (vals), fmt, __VA_ARGS__); \\\n      RTFL_OBJ_PRINT (\"leave\", \"p:s\", this, vals); \\\n   } STMT_END\n\n#define DBG_OBJ_LEAVE_VAL_O(obj, fmt, ...) \\\n   STMT_START { \\\n      char vals[256]; \\\n      snprintf (vals, sizeof (vals), fmt, __VA_ARGS__); \\\n      RTFL_OBJ_PRINT (\"leave\", \"p:s\", obj, vals); \\\n   } STMT_END\n\n#define DBG_OBJ_LEAVE_VAL0(val) \\\n   DBG_OBJ_LEAVE_VAL0_O (this, val)\n\n#define DBG_OBJ_LEAVE_VAL0_O(obj, val) \\\n   RTFL_OBJ_PRINT (\"leave\", \"p:s:\", obj, val)\n\n#define DBG_OBJ_CREATE(klass) \\\n   DBG_OBJ_CREATE_O (this, klass)\n\n#define DBG_OBJ_CREATE_O(obj, klass) \\\n   RTFL_OBJ_PRINT (\"create\", \"p:s\", obj, klass);\n\n#define DBG_OBJ_DELETE() \\\n   DBG_OBJ_DELETE_O (this)\n\n#define DBG_OBJ_DELETE_O(obj) \\\n   RTFL_OBJ_PRINT (\"delete\", \"p\", obj);\n\n#define DBG_OBJ_BASECLASS(klass) \\\n   RTFL_OBJ_PRINT (\"ident\", \"p:p\", this, (klass*)this);\n\n#define DBG_OBJ_ASSOC(parent, child) \\\n   RTFL_OBJ_PRINT (\"assoc\", \"p:p\", parent, child); \\\n\n#define DBG_OBJ_ASSOC_PARENT(parent) \\\n   DBG_OBJ_ASSOC (parent, this);\n\n#define DBG_OBJ_ASSOC_CHILD(child) \\\n   DBG_OBJ_ASSOC (this, child);\n\n#define DBG_OBJ_SET_NUM(var, val) \\\n   DBG_OBJ_SET_NUM_O (this, var, val)\n\n#define DBG_OBJ_SET_NUM_O(obj, var, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s:d\", obj, var, val)\n\n#define DBG_OBJ_SET_SYM(var, val) \\\n   DBG_OBJ_SET_SYM_O (this, var, val)\n\n#define DBG_OBJ_SET_SYM_O(obj, var, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s:s\", obj, var, val)\n\n#define DBG_OBJ_SET_BOOL(var, val) \\\n   DBG_OBJ_SET_BOOL_O (this, var, val)\n\n#define DBG_OBJ_SET_BOOL_O(obj, var, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s:s\", obj, var, (val) ? \"true\" : \"false\")\n\n#define DBG_OBJ_SET_STR(var, val) \\\n   DBG_OBJ_SET_STR_O (this, var, val)\n\n#define DBG_OBJ_SET_STR_O(obj, var, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s:\\\"q\\\"\", obj, var, val)\n\n#define DBG_OBJ_SET_PTR(var, val) \\\n   DBG_OBJ_SET_PTR_O (this, var, val)\n\n#define DBG_OBJ_SET_PTR_O(obj, var, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s:p\", obj, var, val)\n\n#define DBG_OBJ_SET_COL(var, val) \\\n   DBG_OBJ_SET_COL_O (this, var, val)\n\n#define DBG_OBJ_SET_COL_O(obj, var, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s:c\", obj, var, val)\n\n#define DBG_OBJ_ARRSET_NUM(var, ind, val) \\\n   DBG_OBJ_ARRSET_NUM_O (this, var, ind, val)\n\n#define DBG_OBJ_ARRSET_NUM_O(obj, var, ind, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d:d\", obj, var, ind, val)\n\n#define DBG_OBJ_ARRSET_SYM(var, ind, val) \\\n   DBG_OBJ_ARRSET_SYM_O (this, var, ind, val)\n\n#define DBG_OBJ_ARRSET_SYM_O(obj, var, ind, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d:s\", obj, var, ind, val)\n\n#define DBG_OBJ_ARRSET_BOOL(var, ind, val) \\\n   DBG_OBJ_ARRSET_BOOL_O (this, var, ind, val)\n\n#define DBG_OBJ_ARRSET_BOOL_O(obj, var, ind, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d:s\", obj, var, ind, (val) ? \"true\" : \"false\")\n\n#define DBG_OBJ_ARRSET_STR(var, ind, val) \\\n   DBG_OBJ_ARRSET_STR_O (this, var, ind, val)\n\n#define DBG_OBJ_ARRSET_STR_O(obj, var, ind, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d:\\\"q\\\"\", obj, var, ind, val)\n\n#define DBG_OBJ_ARRSET_PTR(var, ind, val) \\\n   DBG_OBJ_ARRSET_PTR_O (this, var, ind, val)\n\n#define DBG_OBJ_ARRSET_PTR_O(obj, var, ind, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d:p\", obj, var, ind, val)\n\n#define DBG_OBJ_ARRSET_COL(var, ind, val) \\\n   DBG_OBJ_ARRSET_COL_O (this, var, ind, val)\n\n#define DBG_OBJ_ARRSET_COL_O(obj, var, ind, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d:c\", obj, var, ind, val)\n\n#define DBG_OBJ_ARRATTRSET_NUM(var, ind, attr, val) \\\n   DBG_OBJ_ARRATTRSET_NUM_O (this, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_NUM_O(obj, var, ind, attr, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d.s:d\", obj, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_SYM(var, ind, attr, val) \\\n   DBG_OBJ_ARRATTRSET_SYM_O (this, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_SYM_O(obj, var, ind, attr, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d.s:s\", obj, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_BOOL(var, ind, attr, val) \\\n   DBG_OBJ_ARRATTRSET_BOOL_O (this, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_BOOL_O(obj, var, ind, attr, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d.s:s\", obj, var, ind, attr, \\\n                   (val) ? \"true\" : \"false\")\n\n#define DBG_OBJ_ARRATTRSET_STR(var, ind, attr, val) \\\n   DBG_OBJ_ARRATTRSET_STR_O (this, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_STR_O(obj, var, ind, attr, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d.s:\\\"q\\\"\", obj, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_PTR(var, ind, attr, val) \\\n   DBG_OBJ_ARRATTRSET_PTR_O (this, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_PTR_O(obj, var, ind, attr, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d.s:p\", obj, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_COL(var, ind, attr, val) \\\n   DBG_OBJ_ARRATTRSET_COL_O (this, var, ind, attr, val)\n\n#define DBG_OBJ_ARRATTRSET_COL_O(obj, var, ind, attr, val) \\\n   RTFL_OBJ_PRINT (\"set\", \"p:s.d.s:c\", obj, var, ind, attr, val)\n\n#define DBG_OBJ_CLASS_COLOR(klass, color) \\\n   RTFL_OBJ_PRINT (\"class-color\", \"s:s\", klass, color)\n\n#else /* DBG_RTFL */\n\n#define STMT_NOP         do { } while (0)\n\n#define DBG_IF_RTFL if(0)\n\n#define DBG_GEN_TIME()                                         STMT_NOP\n#define DBG_OBJ_MSG(aspect, prio, msg)                         STMT_NOP\n#define DBG_OBJ_MSG_O(aspect, prio, obj, msg)                  STMT_NOP\n#define DBG_OBJ_MSGF(aspect, prio, fmt, ...)                   STMT_NOP\n#define DBG_OBJ_MSGF_O(aspect, prio, obj, fmt, ...)            STMT_NOP\n#define DBG_OBJ_MARK(aspect, prio, mark)                       STMT_NOP\n#define DBG_OBJ_MARK_O(aspect, prio, obj, mark)                STMT_NOP\n#define DBG_OBJ_MARKF(aspect, prio, fmt, ...)                  STMT_NOP\n#define DBG_OBJ_MARKF_O(aspect, prio, obj, fmt, ...)           STMT_NOP\n#define DBG_OBJ_MSG_START()                                    STMT_NOP\n#define DBG_OBJ_MSG_START_O(obj)                               STMT_NOP\n#define DBG_OBJ_MSG_END()                                      STMT_NOP\n#define DBG_OBJ_MSG_END_O(obj)                                 STMT_NOP\n#define DBG_OBJ_ENTER0(aspect, prio, funname)                  STMT_NOP\n#define DBG_OBJ_ENTER0_O(aspect, prio, obj, funname)           STMT_NOP\n#define DBG_OBJ_ENTER(aspect, prio, funname, fmt, ...)         STMT_NOP\n#define DBG_OBJ_ENTER_O(aspect, prio, obj, funname, fmt, ...)  STMT_NOP\n#define DBG_OBJ_LEAVE()                                        STMT_NOP\n#define DBG_OBJ_LEAVE_O(obj)                                   STMT_NOP\n#define DBG_OBJ_LEAVE_VAL(fmt, ...)                            STMT_NOP\n#define DBG_OBJ_LEAVE_VAL_O(obj, fmt, ...)                     STMT_NOP\n#define DBG_OBJ_LEAVE_VAL0(val)                                STMT_NOP\n#define DBG_OBJ_LEAVE_VAL0_O(obj, val)                         STMT_NOP\n#define DBG_OBJ_CREATE(klass)                                  STMT_NOP\n#define DBG_OBJ_CREATE_O(obj, klass)                           STMT_NOP\n#define DBG_OBJ_DELETE()                                       STMT_NOP\n#define DBG_OBJ_DELETE_O(obj)                                  STMT_NOP\n#define DBG_OBJ_BASECLASS(klass)                               STMT_NOP\n#define DBG_OBJ_ASSOC(parent, child)                           STMT_NOP\n#define DBG_OBJ_ASSOC_PARENT(parent)                           STMT_NOP\n#define DBG_OBJ_ASSOC_CHILD(child)                             STMT_NOP\n#define DBG_OBJ_SET_NUM(var, val)                              STMT_NOP\n#define DBG_OBJ_SET_NUM_O(obj, var, val)                       STMT_NOP\n#define DBG_OBJ_SET_SYM(var, val)                              STMT_NOP\n#define DBG_OBJ_SET_SYM_O(obj, var, val)                       STMT_NOP\n#define DBG_OBJ_SET_BOOL(var, val)                             STMT_NOP\n#define DBG_OBJ_SET_BOOL_O(obj, var, val)                      STMT_NOP\n#define DBG_OBJ_SET_STR(var, val)                              STMT_NOP\n#define DBG_OBJ_SET_STR_O(obj, var, val)                       STMT_NOP\n#define DBG_OBJ_SET_PTR(var, val)                              STMT_NOP\n#define DBG_OBJ_SET_PTR_O(obj, var, val)                       STMT_NOP\n#define DBG_OBJ_SET_COL(var, val)                              STMT_NOP\n#define DBG_OBJ_SET_COL_O(obj, var, val)                       STMT_NOP\n#define DBG_OBJ_ARRSET_NUM(var, ind, val)                      STMT_NOP\n#define DBG_OBJ_ARRSET_NUM_O(obj, var, ind, val)               STMT_NOP\n#define DBG_OBJ_ARRSET_SYM(var, ind, val)                      STMT_NOP\n#define DBG_OBJ_ARRSET_SYM_O(obj, var, ind, val)               STMT_NOP\n#define DBG_OBJ_ARRSET_BOOL(var, ind, val)                     STMT_NOP\n#define DBG_OBJ_ARRSET_BOOL_O(obj, var, ind, val)              STMT_NOP\n#define DBG_OBJ_ARRSET_STR(var, ind, val)                      STMT_NOP\n#define DBG_OBJ_ARRSET_STR_O(obj, var, ind, val)               STMT_NOP\n#define DBG_OBJ_ARRSET_PTR(var, ind, val)                      STMT_NOP\n#define DBG_OBJ_ARRSET_PTR_O(obj, var, ind, val)               STMT_NOP\n#define DBG_OBJ_ARRSET_COL(var, ind, val)                      STMT_NOP\n#define DBG_OBJ_ARRSET_COL_O(obj, var, ind, val)               STMT_NOP\n#define DBG_OBJ_ARRATTRSET_NUM(var, ind, attr, val)            STMT_NOP\n#define DBG_OBJ_ARRATTRSET_NUM_O(obj, var, ind, attr, val)     STMT_NOP\n#define DBG_OBJ_ARRATTRSET_SYM(var, ind, attr, val)            STMT_NOP\n#define DBG_OBJ_ARRATTRSET_SYM_O(obj, var, ind, attr, val)     STMT_NOP\n#define DBG_OBJ_ARRATTRSET_BOOL(var, ind, attr, val)           STMT_NOP\n#define DBG_OBJ_ARRATTRSET_BOOL_O(obj, var, ind, attr, val)    STMT_NOP\n#define DBG_OBJ_ARRATTRSET_STR(var, ind, attr, val)            STMT_NOP\n#define DBG_OBJ_ARRATTRSET_STR_O(obj, var, ind, attr, val)     STMT_NOP\n#define DBG_OBJ_ARRATTRSET_PTR(var, ind, attr, val)            STMT_NOP\n#define DBG_OBJ_ARRATTRSET_PTR_O(obj, var, ind, attr, val)     STMT_NOP\n#define DBG_OBJ_ARRATTRSET_COL(var, ind, attr, val)            STMT_NOP\n#define DBG_OBJ_ARRATTRSET_COL_O(obj, var, ind, attr, val)     STMT_NOP\n#define DBG_OBJ_CLASS_COLOR(klass, color)                      STMT_NOP\n\n#endif /* DBG_RTFL */\n\n#endif /* __DEBUG_RTFL_HH__ */\n"
  },
  {
    "path": "lout/identity.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"identity.hh\"\n\n#include <stdio.h>\n\nnamespace lout {\nnamespace identity {\n\n// ------------------------\n//    IdentifiableObject\n// ------------------------\n\nusing namespace object;\nusing namespace container::typed;\n\nIdentifiableObject::Class::Class (IdentifiableObject::Class *parent, int id,\n                                  const char *className)\n{\n   this->parent = parent;\n   this->id = id;\n   this->className = className;\n}\n\nvoid IdentifiableObject::Class::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append (\"<class \");\n   sb->append (className);\n   sb->append (\" (\");\n   sb->appendInt (id);\n   sb->append (\")\");\n\n   if (parent) {\n      sb->append (\", parent: \");\n      parent->intoStringBuffer (sb);\n   }\n\n   sb->append (\">\");\n}\n\nHashTable <ConstString, IdentifiableObject::Class>\n   *IdentifiableObject::classesByName =\n      new HashTable<ConstString, IdentifiableObject::Class> (true, true);\nVector <IdentifiableObject::Class> *IdentifiableObject::classesById =\n   new Vector <IdentifiableObject::Class> (16, false);\nIdentifiableObject::Class *IdentifiableObject::currentlyConstructedClass;\n\nIdentifiableObject::IdentifiableObject ()\n{\n   currentlyConstructedClass = NULL;\n}\n\nvoid IdentifiableObject::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append(\"<instance \");\n   sb->appendPointer(this);\n   sb->append(\" of \");\n   sb->append(getClassName());\n   sb->append(\">\");\n}\n\n/**\n * \\brief This method must be called in the constructor for the sub class.\n *    See class comment for details.\n */\nvoid IdentifiableObject::registerName (const char *className, int *classId)\n{\n   ConstString str (className);\n   Class *klass = classesByName->get (&str);\n   if (klass == NULL) {\n      klass = new Class (currentlyConstructedClass, classesById->size (),\n                         className);\n      ConstString *key = new ConstString (className);\n      classesByName->put (key, klass);\n      classesById->put (klass);\n      *classId = klass->id;\n   }\n\n   this->classId = klass->id;\n   *classId = klass->id;\n   currentlyConstructedClass = klass;\n}\n\n/**\n * \\brief Returns, whether this class is an instance of the class, given by\n *    \\em otherClassId, or of a sub class of this class.\n */\nbool IdentifiableObject::instanceOf (int otherClassId)\n{\n   if (otherClassId == -1)\n      // Other class has not been registered yet, while it should have been,\n      // if this class is an instance of it or of a sub-class.\n      return false;\n\n   Class *otherClass = classesById->get (otherClassId);\n\n   if (otherClass == NULL) {\n      fprintf (stderr,\n               \"WARNING: Something got wrong here, it seems that a \"\n               \"CLASS_ID was not initialized properly.\\n\");\n      return false;\n   }\n\n   for (Class *klass = classesById->get (classId); klass != NULL;\n        klass = klass->parent) {\n      if (klass == otherClass)\n         return true;\n   }\n\n   return false;\n}\n\n} // namespace identity\n} // namespace lout\n"
  },
  {
    "path": "lout/identity.hh",
    "content": "#ifndef __LOUT_OBJECTX_HH__\n#define __LOUT_OBJECTX_HH__\n\n#include \"object.hh\"\n#include \"container.hh\"\n#include \"signal.hh\"\n\nnamespace lout {\n\n/**\n * \\brief Some stuff to identify classes of objects at run-time.\n */\nnamespace identity {\n\n/**\n * \\brief Instances of classes, which are sub classes of this class, may\n *    be identified at run-time.\n *\n * <h3>Testing the class</h3>\n *\n * Since e.g. dw::Textblock is a sub class of IdentifiableObject, and\n * implemented in the correct way (as described below), for any given\n * IdentifiableObject the following test can be done:\n *\n * \\code\n * identity::IdentifiableObject *o;\n * // ...\n * bool isATextblock = o->instanceOf(dw::Textblock::CLASS_ID);\n * \\endcode\n *\n * \\em isATextblock is true, when \\em o is an instance of dw::Textblock,\n * or of a sub class of dw::Textblock. Otherwise, \\em isATextblock is false.\n *\n * It is also possible to get the class identifier of an\n * identity::IdentifiableObject, e.g.\n *\n * \\code\n * bool isOnlyATextblock = o->getClassId() == dw::Textblock::CLASS_ID;\n * \\endcode\n *\n * would result in true, if o is an instance of dw::Textblock, but not an\n * instance of a sub class of dw::Textblock.\n *\n * <h3>Defining Sub Classes</h3>\n *\n * Each direct or indirect sub class of IdentifiableObject must\n *\n * <ul>\n * <li> add a static int CLASS_ID with -1 as initial value, and\n * <li> call registerName (\\em name, &CLASS_ID) in the constructor, where\n *      \\em name should be unique, e.g. the fully qualified class name.\n * </ul>\n *\n * After this, <i>class</i>\\::CLASS_ID refers to a number, which denotes the\n * class. (If this is still -1, since the class has not yet been instantiated,\n * any test will fail, which is correct.)\n *\n * <h3>Notes on implementation</h3>\n *\n * If there are some classes like this:\n *\n * \\dot\n * digraph G {\n *    node [shape=record, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"none\", arrowtail=\"empty\", labelfontname=Helvetica,\n *          labelfontsize=10, color=\"#404040\", labelfontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *    IdentifiableObject [color=\"#a0a0a0\"];\n *    A;\n *    B [color=\"#a0a0a0\"];\n *    C;\n *    IdentifiableObject -> A;\n *    IdentifiableObject -> B;\n *    B -> C;\n * }\n * \\enddot\n *\n * <center>[\\ref uml-legend \"legend\"]</center>\n *\n * and first, an instance of A, and then an instance of C is created, there\n * will be the following calls of functions and constructors:\n *\n * <ol>\n * <li> %IdentifiableObject ();\n * <li> %registerName (\"A\", &A::CLASS_ID);\n * <li> %IdentifiableObject ();\n * <li> %registerName (\"B\", &B::CLASS_ID);\n * <li> %registerName (\"C\", &C::CLASS_ID);\n * </ol>\n *\n * From this, the class hierarchy above can easily constructed, and stored\n * in identity::IdentifiableObject::classesByName and\n * in identity::IdentifiableObject::classesById. See the code for details.\n *\n * N.b. Multiple inheritance is not supported, the construction of the\n * tree would become confused.\n */\nclass IdentifiableObject: public object::Object\n{\nprivate:\n   class Class: public object::Object\n   {\n   public:\n      Class *parent;\n      int id;\n      const char *className;\n\n      Class (Class *parent, int id, const char *className);\n\n      void intoStringBuffer(misc::StringBuffer *sb);\n   };\n\n   static container::typed::HashTable <object::ConstString,\n                                       Class> *classesByName;\n   static container::typed::Vector <Class> *classesById;\n   static Class *currentlyConstructedClass;\n\n   int classId;\n\nprotected:\n   void registerName (const char *className, int *classId);\n\npublic:\n   IdentifiableObject ();\n\n   void intoStringBuffer(misc::StringBuffer *sb);\n\n   /**\n    * \\brief Returns the class identifier.\n    *\n    * This is rarely used, rather, tests with\n    * identity::IdentifiableObject::instanceOf are done.\n    */\n   int getClassId () { return classId; }\n\n   /**\n    * \\brief Return the name, under which the class of this object was\n    *    registered.\n    */\n   const char *getClassName() { return classesById->get(classId)->className; }\n\n   bool instanceOf (int otherClassId);\n};\n\n} // namespace identity\n\n} // namespace lout\n\n#endif // __LOUT_OBJECTX_HH__\n"
  },
  {
    "path": "lout/misc.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"misc.hh\"\n\n#include <ctype.h>\n#include <config.h>\n\n#define PRGNAME PACKAGE \"/\" VERSION\n\nnamespace lout {\n\nnamespace misc {\n\nconst char *prgName = PRGNAME;\n\nvoid init (int argc, char *argv[])\n{\n   prgName = strdup (argv[0]);\n}\n\n\n// ------------------\n//    StringBuffer\n// ------------------\n\n\nStringBuffer::StringBuffer()\n{\n   firstNode = lastNode = NULL;\n   numChars = 0;\n   str = NULL;\n   strValid = false;\n}\n\nStringBuffer::~StringBuffer()\n{\n   clear ();\n   if (str)\n      delete[] str;\n}\n\n/**\n * \\brief Append a NUL-terminated string to the buffer, without copying.\n *\n * No copy is made, so this method should only be used in cases, where\n * the string would otherwise be freed again. (This method may then\n * save some CPU cycles.)\n */\nvoid StringBuffer::appendNoCopy(char *str)\n{\n   Node *node = new Node();\n   node->data = str;\n   node->next = NULL;\n\n   if (firstNode == NULL) {\n      firstNode = node;\n      lastNode = node;\n   } else {\n      lastNode->next = node;\n      lastNode = node;\n   }\n\n   numChars += strlen(str);\n   strValid = false;\n}\n\n/**\n * \\brief Return a NUL-terminated strings containing all appended strings.\n *\n * The caller does not have to free the string, this is done in\n * misc::StringBuffer::~StringBuffer.\n */\nconst char *StringBuffer::getChars()\n{\n   if (strValid)\n      return str;\n\n   if (str)\n      delete[] str;\n   str = new char[numChars + 1];\n   char *p = str;\n\n   for (Node *node = firstNode; node; node = node->next) {\n      int l = strlen(node->data);\n      memcpy(p, node->data, l * sizeof(char));\n      p += l;\n   }\n\n   *p = 0;\n   strValid = true;\n   return str;\n}\n\n/**\n * \\brief Remove all strings appended to the string buffer.\n */\nvoid StringBuffer::clear ()\n{\n   Node *node, *nextNode;\n   for (node = firstNode; node; node = nextNode) {\n      nextNode = node->next;\n      free(node->data);\n      delete node;\n   }\n   firstNode = lastNode = NULL;\n   numChars = 0;\n   strValid = false;\n}\n\n\n// ------------\n//    BitSet\n// ------------\n\nBitSet::BitSet(int initBits)\n{\n   numBits = initBits;\n   numBytes = bytesForBits(initBits);\n   bits = (unsigned char*)malloc(numBytes * sizeof(unsigned char));\n   clear();\n}\n\nBitSet::~BitSet()\n{\n  free(bits);\n}\n\nvoid BitSet::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append(\"[\");\n   for (int i = 0; i < numBits; i++)\n      sb->append(get(i) ? \"1\" : \"0\");\n   sb->append(\"]\");\n}\n\nbool BitSet::get(int i) const\n{\n   if (8 * i >= numBytes)\n      return false;\n   else\n      return bits[i / 8] & (1 << (i % 8));\n}\n\nvoid BitSet::set(int i, bool val)\n{\n   if (i > numBits)\n      numBits = i;\n\n   if (8 * i >= numBytes) {\n      int newNumBytes = numBytes;\n      while (8 * i >= newNumBytes)\n         newNumBytes *= 2;\n\n      void *vp;\n      assert((vp = realloc(bits, newNumBytes * sizeof(unsigned char))));\n      if (!vp) exit(-2); // when NDEBUG is defined\n      bits = (unsigned char*)vp;\n      memset(bits + numBytes, 0, newNumBytes - numBytes);\n      numBytes = newNumBytes;\n   }\n\n   if (val)\n      bits[i / 8] |= (1 << (i % 8));\n   else\n      bits[i / 8] &= ~(1 << (i % 8));\n}\n\nvoid BitSet::clear()\n{\n   memset(bits, 0, numBytes);\n}\n\n} // namespace misc\n\n} // namespace lout\n"
  },
  {
    "path": "lout/misc.hh",
    "content": "#ifndef __LOUT_MISC_HH__\n#define __LOUT_MISC_HH__\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n\nnamespace lout {\n\n/**\n * \\brief Miscellaneous stuff, which does not fit anywhere else.\n *\n * Actually, the other parts, beginning with \\ref object, depend on this.\n */\nnamespace misc {\n\ntemplate <class T> inline T min (T a, T b) { return a < b ? a : b; }\ntemplate <class T> inline T max (T a, T b) { return a > b ? a : b; }\n\ntemplate <class T> inline T min (T a, T b, T c)\n{\n   return (min (a, min (b, c)));\n}\ntemplate <class T> inline T max (T a, T b, T c)\n{\n   return (max (a, max (b, c)));\n}\n\nextern const char *prgName;\n\nvoid init (int argc, char *argv[]);\n\ninline void assertNotReached ()\n{\n   fprintf (stderr, \"*** [%s] This should not happen! ***\\n\", prgName);\n   abort ();\n}\n\ninline void assertNotReached (const char *fmt, ...)\n{\n   va_list argp;\n   va_start(argp, fmt);\n\n   fprintf (stderr, \"*** [%s] This should not happen: \", prgName);\n   vfprintf(stderr, fmt, argp);\n   fprintf (stderr, \"! ***\\n\");\n\n   va_end(argp);\n\n   abort ();\n}\n\ninline void notImplemented (const char *name)\n{\n   fprintf (stderr, \"*** [%s] Not implemented: %s ***\\n\", prgName, name);\n   abort ();\n}\n\ninline int roundInt(double d)\n{\n   return (int) ((d > 0) ? (d + 0.5) : (d - 0.5));\n}\n\ninline int AsciiTolower(char c)\n{\n   return ((c >= 'A' && c <= 'Z') ? c + 0x20 : c);\n}\n\ninline int AsciiToupper(char c)\n{\n   return ((c >= 'a' && c <= 'z') ? c - 0x20 : c);\n}\n\ninline int AsciiStrcasecmp(const char *s1, const char *s2)\n{\n   int ret = 0;\n\n   while ((*s1 || *s2) && !(ret = AsciiTolower(*s1) - AsciiTolower(*s2))) {\n      s1++;\n      s2++;\n   }\n   return ret;\n}\n\ninline const char *boolToStr (bool b) { return b ? \"true\" : \"false\"; }\n\n/**\n * \\brief Simple (simpler than container::untyped::Vector and\n *    container::typed::Vector) template based vector.\n */\ntemplate <class T> class SimpleVector\n{\nprivate:\n   T *array;\n   int num, numAlloc;\n\n   inline void resize ()\n   {\n      /* This algorithm was tuned for memory&speed with this huge page:\n       *   http://downloads.mysql.com/docs/refman-6.0-en.html.tar.gz\n       */\n      if (array == NULL) {\n         this->numAlloc = 1;\n         this->array = (T*) malloc (sizeof (T));\n      }\n      if (this->numAlloc < this->num) {\n         this->numAlloc = (this->num < 100) ?\n                          this->num : this->num + this->num/10;\n         this->array =\n            (T*) realloc(this->array, (this->numAlloc * sizeof (T)));\n      }\n   }\n\npublic:\n   inline SimpleVector (int initAlloc = 1)\n   {\n      this->num = 0;\n      this->numAlloc = initAlloc;\n      this->array = NULL;\n   }\n\n   inline SimpleVector (const SimpleVector &o) {\n      this->array = NULL;\n      this->num = o.num;\n      this->numAlloc = o.numAlloc;\n      resize ();\n      memcpy (this->array, o.array, sizeof (T) * num);\n   }\n\n   inline ~SimpleVector ()\n   {\n      if (this->array)\n         free (this->array);\n   }\n\n   /**\n    * \\brief Return the number of elements put into this vector.\n    */\n   inline int size() const { return this->num; }\n\n   inline bool empty() const { return size() == 0; }\n\n   inline T* getArray() const { return array; }\n\n   inline T* detachArray() {\n      T* arr = array;\n      array = NULL;\n      numAlloc = 0;\n      num = 0;\n      return arr;\n   }\n\n   /**\n    * \\brief Increase the vector size by one.\n    *\n    * May be necessary before calling misc::SimpleVector::set.\n    */\n   inline void increase() { setSize(this->num + 1); }\n\n   /**\n    * \\brief Set the size explicitly.\n    *\n    * May be necessary before calling misc::SimpleVector::set.\n    */\n   inline void setSize(int newSize) {\n      assert (newSize >= 0);\n      this->num = newSize;\n      this->resize ();\n   }\n\n   /**\n    * \\brief Set the size explicitly and initialize new values.\n    *\n    * May be necessary before calling misc::SimpleVector::set.\n    */\n   inline void setSize (int newSize, T t) {\n      int oldSize = this->num;\n      setSize (newSize);\n      for (int i = oldSize; i < newSize; i++)\n         set (i, t);\n   }\n\n   /**\n    * \\brief Return the reference of one element.\n    *\n    * \\sa misc::SimpleVector::get\n    */\n   inline T* getRef (int i) const {\n      assert (i >= 0 && this->num - i > 0);\n      return array + i;\n   }\n\n   /**\n    * \\brief Return the one element, explicitly.\n    *\n    * The element is copied, so for complex elements, you should rather used\n    * misc::SimpleVector::getRef.\n    */\n   inline T get (int i) const {\n      assert (i >= 0 && this->num - i > 0);\n      return this->array[i];\n   }\n\n   /**\n    * \\brief Return the reference of the first element (convenience method).\n    */\n   inline T* getFirstRef () const {\n      assert (this->num > 0);\n      return this->array;\n   }\n\n   /**\n    * \\brief Return the first element, explicitly.\n    */\n   inline T getFirst () const {\n      assert (this->num > 0);\n      return this->array[0];\n   }\n\n   /**\n    * \\brief Return the reference of the last element (convenience method).\n    */\n   inline T* getLastRef () const {\n      assert (this->num > 0);\n      return this->array + this->num - 1;\n   }\n\n   /**\n    * \\brief Return the last element, explicitly.\n    */\n   inline T getLast () const {\n      assert (this->num > 0);\n      return this->array[this->num - 1];\n   }\n\n   /**\n    * \\brief Store an object in the vector.\n    *\n    * Unlike in container::untyped::Vector and container::typed::Vector,\n    * you have to care about the size, so a call to\n    * misc::SimpleVector::increase or misc::SimpleVector::setSize may\n    * be necessary before.\n    */\n   inline void set (int i, T t) {\n      assert (i >= 0 && this->num - i > 0);\n      this->array[i] = t;\n   }\n\n   /**\n    * \\brief Store an object at the end of the vector.\n    */\n   inline void setLast (T t) {\n      assert (this->num > 0);\n      this->array[this->num - 1] = t;\n   }\n\n   /**\n    * \\brief Copies some elements into another vector of the same\n    *    type.\n    *\n    * Cannot be used to copy elements within one vector. (For this,\n    * it would have to be extended to copy backwards in some cases.)\n    */\n   inline void copyTo(SimpleVector<T> *dest, int thisStart = 0,\n                      int thisLast = -1, int destStart = 0) {\n      assert (dest != this);\n      if (thisLast == -1)\n         thisLast = this->size () - 1;\n      for (int i = thisStart; i <= thisLast; i++)\n         dest->set (i - thisStart + destStart, get (i));\n   }\n};\n\n/**\n * \\brief Container similar to lout::misc::SimpleVector, but some cases\n *    of insertion optimized (used for hyphenation).\n *\n * For hyphenation, words are often split, so that some space must be\n * inserted by the method NotSoSimpleVector::insert. Typically, some\n * elements are inserted quite at the beginning (when the word at the\n * end of the first or at the beginning of the second line is\n * hyphenated), then, a bit further (end of second line/beginning of\n * third line) and so on. In the first time, nearly all words must be\n * moved; in the second time, a bit less, etc. After all, using a\n * simple vector would result in O(n<sup>2</sup>) number of elements\n * moved total. With this class, however, the number can be kept at\n * O(n).\n *\n * The basic idea is to keep an extra array (actually two, of which\n * the second one is used temporarily), which is inserted in a logical\n * way. Since there is only one extra array at max, reading is rather\n * simple and fast (see NotSoSimpleVector::getRef): check whether the\n * position is before, within, or after the extra array. The first\n * insertion is also rather simple, when the extra array has to be\n * created. The following sketch illustrates the most complex case,\n * when an extra array exists, and something is inserted after it (the\n * case for which this class has been optimized):\n *\n * \\image html not-so-simple-container.png\n *\n * Dotted lines are used to keep the boxes aligned.\n *\n * As you see, only a relatively small fraction of elements has to be\n * moved.\n *\n * There are some other cases, which have to be documented.\n */\ntemplate <class T> class NotSoSimpleVector\n{\nprivate:\n   T *arrayMain, *arrayExtra1, *arrayExtra2;\n   int numMain, numExtra, numAllocMain, numAllocExtra, startExtra;\n\n   inline void resizeMain ()\n   {\n      /* This algorithm was tuned for memory&speed with this huge page:\n       *   http://downloads.mysql.com/docs/refman-6.0-en.html.tar.gz\n       */\n      if (arrayMain == NULL) {\n         this->numAllocMain = 1;\n         this->arrayMain = (T*) malloc (sizeof (T));\n      }\n      if (this->numAllocMain < this->numMain) {\n         this->numAllocMain = (this->numMain < 100) ?\n                          this->numMain : this->numMain + this->numMain/10;\n         this->arrayMain =\n            (T*) realloc(this->arrayMain, (this->numAllocMain * sizeof (T)));\n      }\n   }\n\n   inline void resizeExtra ()\n   {\n      /* This algorithm was tuned for memory&speed with this huge page:\n       *   http://downloads.mysql.com/docs/refman-6.0-en.html.tar.gz\n       */\n      if (arrayExtra1 == NULL) {\n         this->numAllocExtra = 1;\n         this->arrayExtra1 = (T*) malloc (sizeof (T));\n         this->arrayExtra2 = (T*) malloc (sizeof (T));\n      }\n      if (this->numAllocExtra < this->numExtra) {\n         this->numAllocExtra = (this->numExtra < 100) ?\n                          this->numExtra : this->numExtra + this->numExtra/10;\n         this->arrayExtra1 =\n            (T*) realloc(this->arrayExtra1, (this->numAllocExtra * sizeof (T)));\n         this->arrayExtra2 =\n            (T*) realloc(this->arrayExtra2, (this->numAllocExtra * sizeof (T)));\n      }\n   }\n\n   void consolidate ()\n   {\n      if (startExtra != -1) {\n         numMain += numExtra;\n         resizeMain ();\n         memmove (arrayMain + startExtra + numExtra, arrayMain + startExtra,\n                  (numMain - (startExtra + numExtra)) * sizeof (T));\n         memmove (arrayMain + startExtra, arrayExtra1, numExtra * sizeof (T));\n         startExtra = -1;\n         numExtra = 0;\n      }\n   }\n\npublic:\n   inline NotSoSimpleVector (int initAlloc)\n   {\n      this->numMain = this->numExtra = 0;\n      this->numAllocMain = initAlloc;\n      this->numAllocExtra = initAlloc;\n      this->arrayMain = this->arrayExtra1 = this->arrayExtra2 = NULL;\n      this->startExtra = -1;\n   }\n\n   inline NotSoSimpleVector (const NotSoSimpleVector &o)\n   {\n      this->arrayMain = NULL;\n      this->numMain = o.numMain;\n      this->numAllocMain = o.numAllocMain;\n      resizeMain ();\n      memcpy (this->arrayMain, o.arrayMain, sizeof (T) * numMain);\n\n      this->arrayExtra = NULL;\n      this->numExtra = o.numExtra;\n      this->numAllocExtra = o.numAllocExtra;\n      resizeExtra ();\n      memcpy (this->arrayExtra, o.arrayExtra, sizeof (T) * numExtra);\n\n      this->startExtra = o.startExtra;\n   }\n\n   inline ~NotSoSimpleVector ()\n   {\n      if (this->arrayMain)\n         free (this->arrayMain);\n      if (this->arrayExtra1)\n         free (this->arrayExtra1);\n      if (this->arrayExtra2)\n         free (this->arrayExtra2);\n   }\n\n   inline int size() const { return this->numMain + this->numExtra; }\n\n   inline bool empty() const { return size() == 0; }\n\n   inline void increase() { setSize(size() + 1); }\n\n   inline void setSize(int newSize)\n   {\n      assert (newSize >= 0);\n      this->numMain = newSize - numExtra;\n      this->resizeMain ();\n   }\n\n   void insert (int index, int numInsert)\n   {\n      assert (numInsert >= 0);\n\n      // The following lines are a simple (but inefficient) replacement.\n      //setSize (numMain + numInsert);\n      //memmove (arrayMain + index + numInsert, arrayMain + index,\n      //         (numMain - index - numInsert) * sizeof (T));\n      //return;\n\n      if (this->startExtra == -1) {\n         // simple case\n         this->numExtra = numInsert;\n         this->startExtra = index;\n         resizeExtra ();\n      } else {\n         if (index < startExtra) {\n            consolidate ();\n            insert (index, numInsert);\n         } else if (index < startExtra + numExtra) {\n            int oldNumExtra = numExtra;\n            numExtra += numInsert;\n            resizeExtra ();\n\n            int toMove = startExtra + oldNumExtra - index;\n            memmove (arrayExtra1 + numExtra - toMove,\n                     arrayExtra1 + index - startExtra,\n                     toMove * sizeof (T));\n         } else {\n            int oldNumExtra = numExtra;\n            numExtra += numInsert;\n            resizeExtra ();\n\n            // Note: index refers to the *logical* adress, not to the\n            // *physical* one.\n            int diff = index - this->startExtra - oldNumExtra;\n            T *arrayMainI = arrayMain + this->startExtra;\n            for (int i = diff + oldNumExtra - 1; i >= 0; i--) {\n               T *src = i < oldNumExtra ?\n                  this->arrayExtra1 + i : arrayMainI + (i - oldNumExtra);\n               T *dest = i < diff ?\n                  arrayMainI + i : arrayExtra2 + (i - diff);\n               *dest = *src;\n            }\n\n            memcpy (arrayExtra1, arrayExtra2, sizeof (T) * oldNumExtra);\n            startExtra = index - oldNumExtra;\n         }\n      }\n   }\n\n   /**\n    * \\brief Return the reference of one element.\n    *\n    * \\sa misc::SimpleVector::get\n    */\n   inline T* getRef (int i) const\n   {\n      if (this->startExtra == -1) {\n         assert (i >= 0 && i < this->numMain);\n         return this->arrayMain + i;\n      } else {\n         if (i < this->startExtra) {\n            assert (i >= 0);\n            return this->arrayMain + i;\n         } else if (i >= this->startExtra + this->numExtra) {\n            // The original assertion\n            ///\n            //    \"assert (i < this->numMain + this->numExtra)\"\n            //\n            // causes this warnung in dw::Textblock::breakAdded:\n            //\n            //    \"assuming signed overflow does not occur when assuming that\n            //     (X - c) > X is always false [-Wstrict-overflow]\"\n            //\n            // Subtracting numExtra from both sides solves this,\n            // interrestingly.\n\n            assert (i - this->numExtra < this->numMain);\n            return this->arrayMain + i - this->numExtra;\n         } else\n            return this->arrayExtra1 + i - this->startExtra;\n      }\n   }\n\n   /**\n    * \\brief Return the one element, explicitly.\n    *\n    * The element is copied, so for complex elements, you should rather used\n    * misc::SimpleVector::getRef.\n    */\n   inline T get (int i) const\n   {\n      return *(this->getRef(i));\n   }\n\n   /**\n    * \\brief Return the reference of the first element (convenience method).\n    */\n   inline T* getFirstRef () const {\n      assert (size () > 0);\n      return this->getRef(0);\n   }\n\n   /**\n    * \\brief Return the first element, explicitly.\n    */\n   inline T getFirst () const {\n      return *(this->getFirstRef());\n   }\n\n   /**\n    * \\brief Return the reference of the last element (convenience method).\n    */\n   inline T* getLastRef () const {\n      assert (size () > 0);\n      return this->getRef(size () - 1);\n   }\n\n   /**\n    * \\brief Return the last element, explicitly.\n    */\n   inline T getLast () const {\n      return *(this->getLastRef());\n   }\n\n   /**\n    * \\brief Store an object in the vector.\n    *\n    * Unlike in container::untyped::Vector and container::typed::Vector,\n    * you have to care about the size, so a call to\n    * misc::SimpleVector::increase or misc::SimpleVector::setSize may\n    * be necessary before.\n    */\n   inline void set (int i, T t) {\n      *(this->getRef(i)) = t;\n   }\n\n   /**\n    * \\brief Store an object at the end of the vector.\n    */\n   inline void setLast (T t) {\n      *(this->getLastRef()) = t;\n   }\n};\n\n/**\n * \\brief A class for fast concatenation of a large number of strings.\n */\nclass StringBuffer\n{\nprivate:\n   struct Node\n   {\n      char *data;\n      Node *next;\n   };\n\n   Node *firstNode, *lastNode;\n   int numChars;\n   char *str;\n   bool strValid;\n\npublic:\n   StringBuffer();\n   ~StringBuffer();\n\n   /**\n    * \\brief Append a NUL-terminated string to the buffer, with copying.\n    *\n    * A copy is kept in the buffer, so the caller does not have to care\n    * about memory management.\n    */\n   inline void append(const char *str) { appendNoCopy(strdup(str)); }\n   inline void appendInt(int n)\n   { char buf[32]; sprintf (buf, \"%d\", n); append (buf); }\n   inline void appendPointer(void *p)\n   { char buf[32]; sprintf (buf, \"%p\", p); append (buf); }\n   inline void appendBool(bool b) { append (b ? \"true\" : \"false\"); }\n   void appendNoCopy(char *str);\n   const char *getChars();\n   void clear ();\n};\n\n\n/**\n * \\brief A bit set, which automatically reallocates when needed.\n */\nclass BitSet\n{\nprivate:\n   unsigned char *bits;\n   int numBits, numBytes;\n\n   inline int bytesForBits(int bits) { return bits == 0 ? 1 : (bits + 7) / 8; }\n\npublic:\n   BitSet(int initBits);\n   ~BitSet();\n   void intoStringBuffer(misc::StringBuffer *sb);\n   bool get(int i) const;\n   void set(int i, bool val);\n   void clear();\n};\n\n/**\n * \\brief A simple allocator optimized to handle many small chunks of memory.\n * The chunks can not be free'd individually. Instead the whole zone must be\n * free'd with zoneFree().\n */\nclass ZoneAllocator\n{\nprivate:\n   size_t poolSize, poolLimit, freeIdx;\n   SimpleVector <char*> *pools;\n   SimpleVector <char*> *bulk;\n\npublic:\n   ZoneAllocator (size_t poolSize) {\n      this->poolSize = poolSize;\n      this->poolLimit = poolSize / 4;\n      this->freeIdx = poolSize;\n      this->pools = new SimpleVector <char*> (1);\n      this->bulk = new SimpleVector <char*> (1);\n   };\n\n   ~ZoneAllocator () {\n      zoneFree ();\n      delete pools;\n      delete bulk;\n   }\n\n   inline void * zoneAlloc (size_t t) {\n      void *ret;\n\n      if (t > poolLimit) {\n         bulk->increase ();\n         bulk->set (bulk->size () - 1, (char*) malloc (t));\n         return bulk->get (bulk->size () - 1);\n      }\n\n      if (t > poolSize - freeIdx) {\n         pools->increase ();\n         pools->set (pools->size () - 1, (char*) malloc (poolSize));\n         freeIdx = 0;\n      }\n\n      ret = pools->get (pools->size () - 1) + freeIdx;\n      freeIdx += t;\n      return ret;\n   }\n\n   inline void zoneFree () {\n      for (int i = 0; i < pools->size (); i++)\n         free (pools->get (i));\n      pools->setSize (0);\n      for (int i = 0; i < bulk->size (); i++)\n         free (bulk->get (i));\n      bulk->setSize (0);\n      freeIdx = poolSize;\n   }\n\n   inline const char *strndup (const char *str, size_t t) {\n      char *new_str = (char *) zoneAlloc (t + 1);\n      memcpy (new_str, str, t);\n      new_str[t] = '\\0';\n      return new_str;\n   }\n\n   inline const char *strdup (const char *str) {\n      return strndup (str, strlen (str));\n   }\n};\n\n} // namespace misc\n\n} // namespace lout\n\n#endif // __LOUT_MISC_HH__\n"
  },
  {
    "path": "lout/msg.h",
    "content": "#ifndef __MSG_H__\n#define __MSG_H__\n\n#include <stdio.h>\n\n#define prefs_show_msg    1\n\n#define D_STMT_START      do\n#define D_STMT_END        while (0)\n\n/*\n * You can disable any MSG* macro by adding the '_' prefix.\n */\n#define _MSG(...)\n#define _MSG_WARN(...)\n\n\n#define MSG(...)                                   \\\n   D_STMT_START {                                  \\\n      if (prefs_show_msg){                         \\\n         printf(__VA_ARGS__);                      \\\n         fflush (stdout);                          \\\n      }                                            \\\n   } D_STMT_END\n\n#define MSG_WARN(...)                              \\\n   D_STMT_START {                                  \\\n      if (prefs_show_msg)                          \\\n         printf(\"** WARNING **: \" __VA_ARGS__);    \\\n   } D_STMT_END\n\n#define MSG_ERR(...)                               \\\n   D_STMT_START {                                  \\\n      if (prefs_show_msg)                          \\\n         printf(\"** ERROR **: \" __VA_ARGS__);      \\\n   } D_STMT_END\n\n#endif /* __MSG_H__ */\n"
  },
  {
    "path": "lout/object.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"object.hh\"\n#include <stdio.h>\n#include <stdint.h>\n#include <config.h>\n\nnamespace lout {\n\nnamespace object {\n\n// ------------\n//    Object\n// ------------\n\n/**\n * \\brief The destructor is defined as virtual (but not abstract), so that\n *    destruction of Object's works properly.\n */\nObject::~Object()\n{\n}\n\n/**\n * \\brief Returns, whether two objects are equal.\n *\n * The caller should ensure, that this and the object have the same class;\n * this makes casting of \"other\" safe. Typically, an implementation should\n * check this == other first, the caller can assume a fast implementation.\n */\nbool Object::equals(Object *other)\n{\n   misc::assertNotReached ();\n   return false;\n}\n\n/**\n * \\brief Return a hash value for the object.\n */\nint Object::hashValue()\n{\n   fprintf (stderr, \"Object::hashValue() should be implemented.\\n\");\n   return 0;\n}\n\n/**\n * \\brief Return an exact copy of the object.\n */\nObject *Object::clone()\n{\n   misc::assertNotReached ();\n   return NULL;\n}\n\n/**\n * \\brief Use object::Object::intoStringBuffer to return a textual\n *    representation of the object.\n *\n * The caller does not have to free the memory, object::Object is responsible\n * for this.\n */\nconst char *Object::toString()\n{\n   /** \\todo garbage! */\n   misc::StringBuffer sb;\n   intoStringBuffer(&sb);\n   char *s = strdup(sb.getChars());\n   return s;\n}\n\n/**\n * \\brief Store a textual representation of the object in a misc::StringBuffer.\n *\n * This is used by object::Object::toString.\n */\nvoid Object::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append(\"<not further specified object \");\n   sb->appendPointer(this);\n   sb->append(\">\");\n}\n\n/**\n * \\brief Return the number of bytes, this object totally uses.\n */\nsize_t Object::sizeOf()\n{\n   fprintf (stderr, \"Object::sizeOf() should be implemented.\\n\");\n   return sizeof(Object*);\n}\n\n// ----------------\n//    Comparator\n// ----------------\n\nComparator *Comparator::compareFunComparator = NULL;\n\n/**\n * \\brief This static method may be used as compare function for\n *    qsort(3) and bsearch(3), for an array of Object* (Object*[] or\n *    Object**).\n *\n * \"compareFunComparator\" should be set before.\n *\n * \\todo Not reentrant. Consider switching to reentrant variants\n * (qsort_r), and compare function with an additional argument.\n */\nint Comparator::compareFun(const void *p1, const void *p2)\n{\n   return compareFunComparator->compare (*(Object**)p1, *(Object**)p2);\n}\n\n// ------------------------\n//    StandardComparator\n// ------------------------\n\nint StandardComparator::compare(Object *o1, Object *o2)\n{\n   if (o1 && o2)\n      return ((Comparable*)o1)->compareTo ((Comparable*)o2);\n   else if (o1)\n      return 1;\n   else if (o2)\n      return -1;\n   else\n      return 0;\n}\n\nStandardComparator standardComparator;\n\n// -------------\n//    Pointer\n// -------------\n\nbool Pointer::equals(Object *other)\n{\n   return value == ((Pointer*)other)->value;\n}\n\nint Pointer::hashValue()\n{\n/* For some unknown reason, this doesn't compile on some 64bit platforms:\n *\n *  if (sizeof (int) == sizeof (void*))\n *     return (int)value;\n *  else\n *     return ((int*)&value)[0] ^ ((int*)&value)[1];\n */\n#if SIZEOF_VOID_P == 4\n   // Assuming that sizeof(void*) == sizeof(int); on 32 bit systems.\n   return (int)value;\n#else\n   // Assuming that sizeof(void*) == 2 * sizeof(int); on 64 bit\n   // systems (int is still 32 bit).\n   // Combine both parts of the pointer value *itself*, not what it\n   // points to, by first referencing it (operator \"&\"), then\n   // dereferencing it again (operator \"[]\").\n   return ((intptr_t)value >> 32) ^ ((intptr_t)value);\n#endif\n}\n\nvoid Pointer::intoStringBuffer(misc::StringBuffer *sb)\n{\n   char buf[64];\n   snprintf(buf, sizeof(buf), \"%p\", value);\n   sb->append(buf);\n}\n\n// -------------\n//    Integer\n// -------------\n\nbool Integer::equals(Object *other)\n{\n   return value == ((Integer*)other)->value;\n}\n\nint Integer::hashValue()\n{\n   return (int)value;\n}\n\nvoid Integer::intoStringBuffer(misc::StringBuffer *sb)\n{\n   char buf[64];\n   sprintf(buf, \"%d\", value);\n   sb->append(buf);\n}\n\nint Integer::compareTo(Comparable *other)\n{\n   return value - ((Integer*)other)->value;\n}\n\n// -------------\n//    Boolean\n// -------------\n\nbool Boolean::equals(Object *other)\n{\n   bool value2 = ((Boolean*)other)->value;\n   // TODO Does \"==\" work?\n   return (value && value2) || (!value && value2);\n}\n\nint Boolean::hashValue()\n{\n   return value ? 1 : 0;\n}\n\nvoid Boolean::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append(value ? \"true\" : \"false\");\n}\n\nint Boolean::compareTo(Comparable *other)\n{\n   return (value ? 1 : 0) - (((Boolean*)other)->value ? 1 : 0);\n}\n\n// -----------------\n//    ConstString\n// -----------------\n\nbool ConstString::equals(Object *other)\n{\n   ConstString *otherString = (ConstString*)other;\n   return\n      this == other ||\n      (str == NULL && otherString->str == NULL) ||\n      (str != NULL && otherString->str != NULL &&\n       strcmp(str, otherString->str) == 0);\n}\n\nint ConstString::hashValue()\n{\n  return hashValue(str);\n}\n\n\nint ConstString::compareTo(Comparable *other)\n{\n   String *otherString = (String*)other;\n   if (str && otherString->str)\n      return strcmp(str, otherString->str);\n   else if (str)\n      return 1;\n   else if (otherString->str)\n      return -1;\n   else\n      return 0;\n}\n\n\nint ConstString::hashValue(const char *str)\n{\n   if (str) {\n      int h = 0;\n      for (int i = 0; str[i]; i++)\n         h = (h * 256 + str[i]);\n      return h;\n   } else\n      return 0;\n}\n\nvoid ConstString::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append(str);\n}\n\n// ------------\n//    String\n// ------------\n\nString::String (const char *str): ConstString (str ? strdup(str) : NULL)\n{\n}\n\nString::~String ()\n{\n  if (str)\n    free((char *)str);\n}\n\n// ------------\n//    Pair\n// ------------\n\nPairBase::PairBase(Object *first, Object *second)\n{\n   this->first = first;\n   this->second = second;\n}\n\nPairBase::~PairBase()\n{\n   if (first)\n      delete first;\n   if (second)\n      delete second;\n}\n\nbool PairBase::equals(Object *other)\n{\n   PairBase *otherPair = (PairBase*)other;\n\n   return\n      // Identical?\n      this == other || (\n      (// Both first parts are NULL, ...\n         (first == NULL && otherPair->first == NULL) ||\n         // ... or both first parts are not NULL and equal\n         (first != NULL && otherPair->first != NULL\n          && first->equals (otherPair->first))) &&\n      // Same with second part.\n      ((second == NULL && otherPair->second == NULL) ||\n       (second != NULL && otherPair->second != NULL\n        && second->equals (otherPair->second))));\n}\n\nint PairBase::hashValue()\n{\n   int value = 0;\n\n   if (first)\n      value ^= first->hashValue();\n   if (second)\n      value ^= second->hashValue();\n\n   return value;\n}\n\nvoid PairBase::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append(\"<pair: \");\n\n   if (first)\n      first->intoStringBuffer(sb);\n   else\n      sb->append(\"(nil)\");\n\n   sb->append(\",\");\n\n   if (second)\n      second->intoStringBuffer(sb);\n   else\n      sb->append(\"(nil)\");\n\n   sb->append(\">\");\n}\n\nsize_t PairBase::sizeOf()\n{\n   size_t size = 0;\n\n   if (first)\n      size += first->sizeOf();\n   if (second)\n      size += second->sizeOf();\n\n   return size;\n}\n\n} // namespace object\n\n} // namespace lout\n"
  },
  {
    "path": "lout/object.hh",
    "content": "#ifndef __LOUT_OBJECT_HH__\n#define __LOUT_OBJECT_HH__\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"misc.hh\"\n\nnamespace lout {\n\n/**\n * \\brief Here, some common classes (or interfaces) are defined, to standardize\n *    the access to other classes.\n */\nnamespace object {\n\n/**\n * \\brief This is the base class for many other classes, which defines very\n *    common virtual methods.\n *\n * For convenience, none of them are abstract, but they\n * must be defined, when they are needed, especially for containers.\n */\nclass Object\n{\npublic:\n   virtual ~Object();\n   virtual bool equals(Object *other);\n   virtual int hashValue();\n   virtual Object *clone();\n   virtual void intoStringBuffer(misc::StringBuffer *sb);\n   const char *toString();\n   virtual size_t sizeOf();\n};\n\n/**\n * \\brief Instances of a sub class of may be compared (less, greater).\n *\n * Used for sorting etc.\n */\nclass Comparable: public Object\n{\npublic:\n   /**\n    * \\brief Compare two objects, this and other.\n    *\n    * Return a value < 0, when this is less than other, a value > 0,\n    * when this is greater than other, or 0, when this and other are\n    * equal.\n    *\n    * If c1.equals(c2) (as defined in Object), c1.compareTo(c2) must\n    * be 0, but, unlike you may expect, the reversed is not\n    * necessarily true. This method returns 0, if, according to the\n    * rules for sorting, there is no difference, but there may still\n    * be differences (not relevant for sorting), which \"equals\" will\n    * care about.\n    */\n   virtual int compareTo(Comparable *other) = 0;\n};\n\n/**\n * \\brief Used for other orders as the one defined by Comparable.\n *\n * Compared objects must not neccessary be instances of Comparable.\n */\nclass Comparator: public Object\n{\npublic:\n   /**\n    * \\brief Compare two objects o1 and o2.\n    *\n    * Return a value < 0, when o1 is less than o2, a value > 0, when o1\n    * is greater than o2, or 0, when o1 and o2 are equal.\n    *\n    * If o1.equals(o2) (as defined in Object), compare(o1, o2) must be\n    * 0, but, unlike you may expect, the reversed is not necessarily\n    * true. This method returns 0, if, according to the rules for\n    * sorting, there is no difference, but there may still be\n    * differences (not relevant for sorting), which \"equals\" will care\n    * about.\n    */\n   virtual int compare(Object *o1, Object *o2) = 0;\n\n   static Comparator *compareFunComparator;\n   static int compareFun(const void *p1, const void *p2);\n};\n\nclass StandardComparator: public Comparator\n{\npublic:\n   int compare(Object *o1, Object *o2);\n};\n\nextern StandardComparator standardComparator;\n\n/**\n * \\brief An object::Object wrapper for void pointers.\n */\nclass Pointer: public Object\n{\nprivate:\n   void *value;\n\npublic:\n   Pointer(void *value) { this->value = value; }\n   bool equals(Object *other);\n   int hashValue();\n   void intoStringBuffer(misc::StringBuffer *sb);\n   inline void *getValue() { return value; }\n};\n\n/**\n * \\brief A typed version of object::Pointer.\n */\ntemplate <class T> class TypedPointer: public Pointer\n{\npublic:\n   inline TypedPointer(T *value) : Pointer ((void*)value) { }\n   inline T *getTypedValue() { return (T*)getValue(); }\n};\n\n\n/**\n * \\brief An object::Object wrapper for int's.\n */\nclass Integer: public Comparable\n{\n   int value;\n\npublic:\n   Integer(int value) { this->value = value; }\n   bool equals(Object *other);\n   int hashValue();\n   void intoStringBuffer(misc::StringBuffer *sb);\n   int compareTo(Comparable *other);\n   inline int getValue() { return value; }\n};\n\n\n/**\n * \\brief An object::Object wrapper for bool's.\n */\nclass Boolean: public Comparable\n{\n   bool value;\n\npublic:\n   Boolean(bool value) { this->value = value; }\n   bool equals(Object *other);\n   int hashValue();\n   void intoStringBuffer(misc::StringBuffer *sb);\n   int compareTo(Comparable *other);\n   inline bool getValue() { return value; }\n};\n\n\n/**\n * \\brief An object::Object wrapper for constant strings (char*).\n *\n * As opposed to object::String, the char array is not copied.\n */\nclass ConstString: public Comparable\n{\nprotected:\n   const char *str;\n\npublic:\n   ConstString(const char *str) { this->str = str; }\n   bool equals(Object *other);\n   int hashValue();\n   int compareTo(Comparable *other);\n   void intoStringBuffer(misc::StringBuffer *sb);\n\n   inline const char *chars() { return str; }\n\n   static int hashValue(const char *str);\n};\n\n\n/**\n * \\brief An object::Object wrapper for strings (char*).\n *\n * As opposed to object::ConstantString, the char array is copied.\n */\nclass String: public ConstString\n{\npublic:\n   String(const char *str);\n   ~String();\n};\n\n/**\n * \\todo Comment\n */\nclass PairBase: public Object\n{\nprotected:\n   Object *first, *second;\n\npublic:\n   PairBase(Object *first, Object *second);\n   ~PairBase();\n\n   bool equals(Object *other);\n   int hashValue();\n   void intoStringBuffer(misc::StringBuffer *sb);\n   size_t sizeOf();\n};\n\n/**\n * \\todo Comment\n */\nclass Pair: public PairBase\n{\npublic:\n   Pair(Object *first, Object *second): PairBase (first, second) { }\n\n   inline Object *getFirst () { return first; }\n   inline Object *getSecond () { return second; }\n};\n\n/**\n * \\todo Comment\n */\ntemplate <class F, class S> class TypedPair: public PairBase\n{\npublic:\n   TypedPair(F *first, S *second): PairBase (first, second) { }\n\n   inline F *getFirst () { return first; }\n   inline S *getSecond () { return second; }\n};\n\n} // namespace object\n\n} // namespace lout\n\n#endif // __LOUT_OBJECT_HH__\n"
  },
  {
    "path": "lout/signal.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"signal.hh\"\n\nnamespace lout {\nnamespace signal {\n\nusing namespace container::typed;\n\n// ------------\n//    Emitter\n// ------------\n\nEmitter::Emitter ()\n{\n   receivers = new List <Receiver> (false);\n}\n\nEmitter::~Emitter ()\n{\n   for (Iterator<Receiver> it = receivers->iterator (); it.hasNext (); ) {\n      Receiver *receiver = it.getNext ();\n      receiver->unconnectFrom (this);\n   }\n   delete receivers;\n}\n\nvoid Emitter::intoStringBuffer(misc::StringBuffer *sb)\n{\n   sb->append (\"<emitter: \");\n   receivers->intoStringBuffer (sb);\n   sb->append (\">\");\n}\n\nvoid Emitter::unconnect (Receiver *receiver)\n{\n   receivers->removeRef (receiver);\n}\n\n/**\n * \\brief Connect a receiver to the emitter.\n *\n * This is protected, a sub class should define a wrapper, with the respective\n * receiver as an argument, to gain type safety.\n */\nvoid Emitter::connect (Receiver *receiver)\n{\n   receivers->append (receiver);\n   receiver->connectTo (this);\n}\n\n/**\n * \\brief Emit a void signal.\n *\n * This method should be called by a wrapper (return value void), which\n * \\em folds the signal, and delegates the emission to here.\n */\nvoid Emitter::emitVoid (int signalNo, int argc, Object **argv)\n{\n   for (Iterator <Receiver> it = receivers->iterator (); it.hasNext (); ) {\n      Receiver *receiver = it.getNext();\n      emitToReceiver (receiver, signalNo, argc, argv);\n   }\n}\n\n/**\n * \\brief Emit a boolean signal.\n *\n * This method should be called by a wrapper, which \\em folds the signal,\n * delegates the emission to here, and returns the same boolean value.\n */\nbool Emitter::emitBool (int signalNo, int argc, Object **argv)\n{\n   bool b = false, bt;\n\n   for (Iterator <Receiver> it = receivers->iterator (); it.hasNext (); ) {\n      Receiver *receiver = it.getNext();\n      // Note: All receivers are called, even if one returns true.\n      // Therefore, something like\n      //    b = b || emitToReceiver (receiver, signalNo, argc, argv);\n      // does not work.\n      bt = emitToReceiver (receiver, signalNo, argc, argv);\n      b = b || bt;\n   }\n\n   return b;\n}\n\n\n// --------------\n//    Receiver\n// --------------\n\nReceiver::Receiver()\n{\n   emitters = new List <Emitter> (false);\n}\n\nReceiver::~Receiver()\n{\n   for (Iterator<Emitter> it = emitters->iterator(); it.hasNext(); ) {\n      Emitter *emitter = it.getNext();\n      emitter->unconnect (this);\n   }\n   delete emitters;\n}\n\nvoid Receiver::intoStringBuffer(misc::StringBuffer *sb)\n{\n   // emitters are not listed, to prevent recursion\n   sb->append (\"<receiver>\");\n}\n\nvoid Receiver::connectTo(Emitter *emitter)\n{\n   emitters->append (emitter);\n}\n\nvoid Receiver::unconnectFrom(Emitter *emitter)\n{\n   emitters->removeRef (emitter);\n}\n\n// ------------------------\n//    ObservedObject\n// ------------------------\n\nbool ObservedObject::DeletionEmitter::emitToReceiver (Receiver *receiver,\n                                                      int signalNo,\n                                                      int argc, Object **argv)\n{\n   object::TypedPointer <ObservedObject> *p =\n      (object::TypedPointer<ObservedObject>*)argv[0];\n   ((DeletionReceiver*)receiver)->deleted (p->getTypedValue ());\n   return false;\n}\n\nvoid ObservedObject::DeletionEmitter::emitDeletion (ObservedObject *obj)\n{\n   object::TypedPointer <ObservedObject> p(obj);\n   object::Object *argv[1] = { &p };\n   emitVoid (0, 1, argv);\n}\n\nObservedObject::~ObservedObject()\n{\n   deletionEmitter.emitDeletion (this);\n}\n\n} // namespace signal\n} // namespace lout\n"
  },
  {
    "path": "lout/signal.hh",
    "content": "#ifndef __LOUT_SIGNALS_HH__\n#define __LOUT_SIGNALS_HH__\n\n#include \"object.hh\"\n#include \"container.hh\"\n\nnamespace lout {\n\n/**\n * \\brief This namespace provides base classes to define signals.\n *\n * By using signals, objects may be connected at run-time, e.g. a general\n * button widget may be connected to another application-specific object,\n * which reacts on the operations on the button by the user. In this case,\n * the button e.g. defines a signal \"clicked\", which is \"emitted\" each\n * time the user clicks on the button. After the application-specific\n * object has been connected to this signal, a specific method of it will\n * be called each time, this button emits the \"clicked\" signal.\n *\n * Below, we will call the level, on which signals are defined, the\n * \"general level\", and the level, on which the signals are connected,\n * the \"caller level\".\n *\n * <h3>Defining Signals</h3>\n *\n * Typically, signals are grouped. To define a signal group \\em bar for your\n * class \\em Foo, you have to define two classes, the emitter and the\n * receiver (BarEmitter and BarReceiver), and instantiate the emitter:\n *\n * \\dot\n * digraph G {\n *    node [shape=record, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"none\", arrowtail=\"empty\", labelfontname=Helvetica,\n *          labelfontsize=10, color=\"#404040\", labelfontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *\n *    subgraph cluster_signal {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"signal\";\n *\n *       Emitter [color=\"#a0a0a0\", URL=\"\\ref signal::Emitter\"];\n *       Receiver [color=\"#a0a0a0\", URL=\"\\ref signal::Receiver\"];\n *    }\n *\n *    subgraph cluster_foo {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"General (foo)\";\n *\n *       Foo;\n *       BarEmitter;\n *       BarReceiver [color=\"#a0a0a0\"];\n *    }\n *\n *    Emitter -> BarEmitter;\n *    Receiver -> BarReceiver;\n *    Foo -> BarEmitter [arrowhead=\"open\", arrowtail=\"none\",\n *                       headlabel=\"1\", taillabel=\"1\"];\n * }\n * \\enddot\n *\n * <center>[\\ref uml-legend \"legend\"]</center>\n *\n * BarEmitter (class and instance) may be kept private, but BarReceiver must\n * be public, since the caller of Foo must create a sub class of it. For\n * BarEmitter, several methods must be implemented, see signal::Emitter for\n * details. In BarReceiver, only some virtual abstract methods are defined,\n * which the caller must implement. In this case, it is recommended to define\n * a connectBar(BarReceiver*) method in Foo, which is delegated to the\n * BarEmitter.\n *\n * <h3>Connecting to Signals</h3>\n *\n * A caller, which wants to connect to a signal, must define a sub class of\n * the receiver, and implement the virtual methods. A typical design looks\n * like this:\n *\n * \\dot\n * digraph G {\n *    node [shape=record, fontname=Helvetica, fontsize=10];\n *    edge [arrowhead=\"open\", arrowtail=\"none\", labelfontname=Helvetica,\n *          labelfontsize=10, color=\"#404040\", labelfontcolor=\"#000080\"];\n *    fontname=Helvetica; fontsize=10;\n *\n *    subgraph cluster_foo {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"Generall (foo)\";\n *\n *       BarReceiver [color=\"#a0a0a0\"];\n *    }\n *\n *    subgraph cluster_qix {\n *       style=\"dashed\"; color=\"#000080\"; fontname=Helvetica; fontsize=10;\n *       label=\"Caller (qix)\";\n *\n *       Qix;\n *       QixBarReceiver;\n *    }\n *\n *    BarReceiver -> QixBarReceiver [arrowhead=\"none\", arrowtail=\"empty\"];\n *    QixBarReceiver -> Qix [headlabel=\"1\", taillabel=\"*\"];\n * }\n * \\enddot\n *\n * <center>[\\ref uml-legend \"legend\"]</center>\n *\n * (We skip \"baz\" in the canon, for better readability.)\n *\n * Here, the QixBarReceiver is connected to the Qix, so that the signals can\n * be delegated to the Qix. Notice that the receiver gets automatically\n * disconnected, when deleted (see signal::Receiver::~Receiver).\n *\n * <h3>Void and Boolean Signals</h3>\n *\n * In the simplest case, signal emitting involves calling a list of\n * signal receivers (void signals). For boolean signals, the receivers return\n * a boolean value, and the result of the signal emission (the return value of\n * signal::Emitter::emitBool) returns the disjunction of the values returned\n * by the receivers. Typically, a receiver states with its return value,\n * whether the signal was used in any way, the resulting return value so\n * indicates, whether at least one receiver has used the signal.\n *\n * In Dw, events are processed this way. In the simplest case, they are\n * delegated to the parent widget, if the widget does not process them (by\n * returning false). As an addition, signals are emitted, and if a receiver\n * processes the event, this is handled the same way, as if the widget itself\n * would have processed it.\n *\n * Notice, that also for boolean signals, all receivers are called, even\n * after one receiver has already returned true.\n *\n * <h3>Memory Management</h3>\n *\n * <h4>Emitters</h4>\n *\n * Emitters are typically instantiated one, for one object emitting the\n * signals. In the example above, the class Foo will contain a field\n * \"BarEmitter barEmitter\" (not as a pointer, \"BarEmitter *barEmitter\").\n *\n * <h4>Receivers</h4>\n *\n * It is important, that a emitter never deletes a receiver, it just removes\n * them from the receivers list. Likewise, when a receiver is deleted, it\n * unconnects itself from all emitters. (The same receiver instance can indeed\n * be connected to multiple emitters.) So, the caller has to care about\n * deleting receivers.\n *\n * In the example above, something like that will work:\n *\n * \\code\n * class Qix\n * {\n * private:\n *    class QixBarReceiver\n *    {\n *    public:\n *        Qix *qix;\n *        // ...\n *    };\n *\n *    QixBarReceiver barReceiver;\n *\n *    // ...\n * };\n * \\endcode\n *\n * The constructor of Qix should then set \\em qix:\n *\n * \\code\n * Qix::Qix ()\n * {\n *    barReceiver.qix = this.\n *    // ...\n * }\n * \\endcode\n *\n * After this, &\\em barReceiver can be connected to all instances of\n * BarEmitter, also multiple times.\n */\nnamespace signal {\n\nclass Receiver;\n\n/**\n * \\brief The base class for signal emitters.\n *\n * If defining a signal group, a sub class of this class must be defined,\n * with\n *\n * <ul>\n * <li> a definition of the different signals (as enumeration),\n * <li> an implementation of signal::Emitter::emitToReceiver,\n * <li> wrappers for signal::Emitter::emitVoid and signal::Emitter::emitBool,\n *      respectively (one for each signal), and\n * <li> a wrapper for signal::Emitter::connect.\n * </ul>\n *\n * There are two representations of signals:\n *\n * <ul>\n * <li> In the \\em unfolded representation, the signal itself is represented\n *      by the method itself (in the emitter or the receiver), and the\n *      arguments are represented as normal C++ types.\n *\n * <li> \\em Folding signals means to represent the signal itself by an integer\n *      number (enumeration), and translate the arguments in an object::Object*\n *      array. (If a given argument is not an instance of object::Object*,\n *      the wrappers in \\ref object can be used.)\n * </ul>\n *\n * \\sa \\ref signal\n */\nclass Emitter: public object::Object\n{\n   friend class Receiver;\n\nprivate:\n   container::typed::List <Receiver> *receivers;\n\n   void unconnect (Receiver *receiver);\n\nprotected:\n   void emitVoid (int signalNo, int argc, Object **argv);\n   bool emitBool (int signalNo, int argc, Object **argv);\n   void connect(Receiver *receiver);\n\n   /**\n    * \\brief A sub class must implement this for a call to a single\n    *    receiver.\n    *\n    * This methods gets the signal in a \\em folded representation, it has\n    * to unfold it, and pass it to a single receiver. For boolean signals,\n    * the return value of the receiver must be returned, for void signals,\n    * the return value is discarded.\n    */\n   virtual bool emitToReceiver (Receiver *receiver, int signalNo,\n                                int argc, Object **argv) = 0;\n\npublic:\n   Emitter();\n   ~Emitter();\n\n   void intoStringBuffer(misc::StringBuffer *sb);\n};\n\n/**\n * \\brief The base class for signal receiver base classes.\n *\n * If defining a signal group, a sub class of this class must be defined,\n * in which only the abstract signal methods must be defined.\n *\n * \\sa \\ref signal\n */\nclass Receiver: public object::Object\n{\n  friend class Emitter;\n\nprivate:\n   container::typed::List<Emitter> *emitters;\n\n   void connectTo(Emitter *emitter);\n   void unconnectFrom(Emitter *emitter);\n\npublic:\n   Receiver();\n   ~Receiver();\n\n   void intoStringBuffer(misc::StringBuffer *sb);\n};\n\n/**\n * \\brief An observed object has a signal emitter, which tells the\n *    receivers, when the object is deleted.\n */\nclass ObservedObject\n{\npublic:\n   class DeletionReceiver: public signal::Receiver\n   {\n   public:\n      virtual void deleted (ObservedObject *object) = 0;\n   };\n\nprivate:\n   class DeletionEmitter: public signal::Emitter\n   {\n   protected:\n      bool emitToReceiver (signal::Receiver *receiver, int signalNo,\n                           int argc, Object **argv);\n\n   public:\n      inline void connectDeletion (DeletionReceiver *receiver)\n      { connect (receiver); }\n\n      void emitDeletion (ObservedObject *obj);\n   };\n\n   DeletionEmitter deletionEmitter;\n\npublic:\n   virtual ~ObservedObject();\n\n   inline void connectDeletion (DeletionReceiver *receiver)\n   { deletionEmitter.connectDeletion (receiver); }\n};\n\n} // namespace signal\n\n} // namespace lout\n\n#endif // __LOUT_SIGNALS_HH__\n"
  },
  {
    "path": "lout/unicode.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2012, 2013 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n#include \"unicode.hh\"\n#include \"misc.hh\"\n\nusing namespace lout::misc;\n\nnamespace lout {\n\nnamespace unicode {\n\nstatic unsigned char alpha[0x500] = {\n   // 0000-007F: C0 Controls and Basic Latin\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0xfe, 0xff, 0xff, 0x07, 0xfe, 0xff, 0xff, 0x07,\n   // 0080-00FF: C1 Controls and Latin-1 Supplement\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0x7f, 0xff,\n   // 0100-017F: Latin Extended-A\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   // 0180-024F: Latin Extended-B\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0xff, 0xff,\n   // 0250–02AF: IPA Extensions\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0xff, 0xff, 0xff, 0xff,\n   // 02B0–02FF: Spacing Modifier Letters\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0x00, 0x00,\n   // 0300–036F: Combining Diacritical Marks\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n   // 0370–03FF: Greek and Coptic\n   0xcf, 0x00, 0x40, 0x7d, 0xff, 0xff, 0xfb, 0xff,\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0xff, 0xff, 0xff, 0xff,\n   // 0400–04FF: Cyrillic\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0x03, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff\n};\n\n/**\n * Returns whether a given unicode character is an alphabetic character.\n */\nbool isAlpha (int ch)\n{\n   return ch < 0x500 && (alpha[ch / 8] & (1 << (ch & 7)));\n}\n\nint decodeUtf8 (const char *s)\n{\n   if((s[0] & 0x80) == 0)\n      return s[0];\n   else if((s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80)\n      return ((s[0] & 0x1f) << 6) | (s[1] & 0x3f);\n   else if((s[0] & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80\n           && (s[2] & 0xc0) == 0x80)\n      return ((s[0] & 0x0f) << 12) | ((s[1] & 0x3f) << 6)  | (s[2] & 0x3f);\n   else if((s[0] & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80\n           && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80)\n      return ((s[0] & 0x0f) << 18) | ((s[1] & 0x3f) << 12)\n         | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f);\n   else\n      // Treat as ISO-8859-1 / ISO-8859-15 / Windows-1252\n      return s[0];\n}\n\n\nint decodeUtf8 (const char *s, int len)\n{\n   if(len >= 1 && (s[0] & 0x80) == 0)\n      return s[0];\n   else if(len >= 2 && (s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80)\n      return ((s[0] & 0x1f) << 6) | (s[1] & 0x3f);\n   else if(len >= 3 && (s[0] & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80\n           && (s[2] & 0xc0) == 0x80)\n      return ((s[0] & 0x0f) << 12) | ((s[1] & 0x3f) << 6)  | (s[2] & 0x3f);\n   else if(len >= 4 && (s[0] & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80\n           && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80)\n      return ((s[0] & 0x0f) << 18) | ((s[1] & 0x3f) << 12)\n         | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f);\n   else\n      // Treat as ISO-8859-1 / ISO-8859-15 / Windows-1252\n      return s[0];\n}\n\nconst char *nextUtf8Char (const char *s)\n{\n   const char *r;\n\n   if (s == NULL || s[0] == 0)\n      r = NULL;\n   else if((s[0] & 0x80) == 0)\n      r = s + 1;\n   else if((s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80)\n      r = s + 2;\n   else if((s[0] & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80\n           && (s[2] & 0xc0) == 0x80)\n      r = s + 3;\n   else if((s[0] & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80\n           && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80)\n      r = s + 4;\n   else\n      // invalid UTF-8 sequence: treat as one byte.\n      r = s + 1;\n\n   if (r && r[0] == 0)\n      return NULL;\n   else\n      return r;\n}\n\nconst char *nextUtf8Char (const char *s, int len)\n{\n   const char *r;\n\n   if (s == NULL || len <= 0)\n      r = NULL;\n   else if(len >= 1 && (s[0] & 0x80) == 0)\n      r = s + 1;\n   else if(len >= 2 && (s[0] & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80)\n      r = s + 2;\n   else if(len >= 3 && (s[0] & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80\n           && (s[2] & 0xc0) == 0x80)\n      r = s + 3;\n   else if(len >= 4 && (s[0] & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80\n           && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80)\n      r = s + 4;\n   else\n      // invalid UTF-8 sequence: treat as one byte.\n      r = s + 1;\n\n   if (r && r - s >= len)\n      return NULL;\n   else\n      return r;\n}\n\nint numUtf8Chars (const char *s)\n{\n   int numUtf8 = 0;\n   for (const char *r = s; r; r = nextUtf8Char (r))\n      numUtf8++;\n   return numUtf8;\n}\n\nint numUtf8Chars (const char *s, int len)\n{\n   int numUtf8 = 0;\n   for (const char *r = s; len > 0 && r; r = nextUtf8Char (r, len))\n      numUtf8++;\n   return numUtf8;\n}\n\n} // namespace lout\n\n} // namespace unicode\n"
  },
  {
    "path": "lout/unicode.hh",
    "content": "#ifndef __UNICODE_HH__\n#define __UNICODE_HH__\n\nnamespace lout {\n\n/**\n * \\brief Stuff dealing with Unicode characters: UTF-8, character classes etc.\n *\n */\nnamespace unicode {\n\nbool isAlpha (int ch);\n\nint decodeUtf8 (const char *s);\n\nint decodeUtf8 (const char *s, int len);\n\nconst char *nextUtf8Char (const char *s);\n\nconst char *nextUtf8Char (const char *s, int len);\n\nint numUtf8Chars (const char *s);\n\nint numUtf8Chars (const char *s, int len);\n\n} // namespace lout\n\n} // namespace unicode\n\n#endif // __UNICODE_HH__\n"
  },
  {
    "path": "pkgs/obsd/+DESC",
    "content": "fast and light graphical web browser with additional features\nDillo Plus is a multi-platform graphical web browser known for its speed and\nsmall size. It is written in C and C++ and based on FLTK.\n"
  },
  {
    "path": "pkgs/obsd/+REQUIRING",
    "content": "fltk-1.3.3p3\ndesktop-file-utils-0.26\njpeg-2.1.5.1v0\npng-1.6.39\nlibiconv-1.17\n"
  },
  {
    "path": "pkgs/obsd/Makefile",
    "content": "all: +CONTENTS\n\tpkg_create -f +CONTENTS\n\n+CONTENTS:\n\tsh gen_contents.sh\n\nclean:\n\trm -rf +CONTENTS *.tgz\n\n"
  },
  {
    "path": "pkgs/obsd/gen_contents.sh",
    "content": "#!/bin/sh\n\nBINNAME=dillo-plus\n\nfunction get_sha {\n\tsha256 -b \"$1\" | cut -d ' ' -f 4\n}\n\nfunction get_size {\n\tstat \"$1\" | cut -d ' ' -f 8\n}\n\nfunction gen_file {\n\tlocal prefix=\"$1\"\n\tlocal path=\"$2\"\n\techo \"$path\"\n\techo -n \"@sha \"\n\techo $(get_sha \"$prefix$path\")\n\techo -n \"@size \"\n\techo $(get_size \"$prefix$path\")\n}\n\ncat  >+CONTENTS <<EOF\n@name dillo-plus-3.3.0-custom\n@version 10\n@arch amd64\n$(gen_file \"\" +DESC)\n@depend converters/libiconv:libiconv-*:libiconv-1.17\n@depend graphics/jpeg:jpeg-*:jpeg-2.1.5.1v0\n@depend graphics/png:png-*:png-1.6.39\n@depend x11/fltk:fltk-*:fltk-1.3.3p3\n@wantlib X11.18.0\n@wantlib Xau.10.0\n@wantlib Xcursor.5.0\n@wantlib Xdmcp.11.0\n@wantlib Xext.13.0\n@wantlib Xfixes.6.1\n@wantlib Xft.12.0\n@wantlib Xinerama.6.0\n@wantlib c++.9.0\n@wantlib c++abi.6.0\n@wantlib c.97.1\n@wantlib crypto.52.0\n@wantlib fltk.8.0\n@wantlib fontconfig.13.1\n@wantlib iconv.7.1\n@wantlib jpeg.70.1\n@wantlib m.10.1\n@wantlib png.18.0\n@wantlib pthread.27.1\n@wantlib ssl.55.0\n@wantlib z.7.0\n@cwd /usr/local\n@bin $(gen_file \"/usr/local/\" bin/$BINNAME)\n@bin $(gen_file \"/usr/local/\" bin/dillo-install-hyphenation)\n@bin $(gen_file \"/usr/local/\" bin/dpid-plus)\n@bin $(gen_file \"/usr/local/\" bin/dpidc-plus)\nlib/$BINNAME/\nlib/$BINNAME/dpi/\nlib/$BINNAME/dpi/bookmarks/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/bookmarks/bookmarks.dpi)\nlib/$BINNAME/dpi/cookies/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/cookies/cookies.dpi)\nlib/$BINNAME/dpi/datauri/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/datauri/datauri.filter.dpi)\nlib/$BINNAME/dpi/downloads/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/downloads/downloads.dpi)\nlib/$BINNAME/dpi/file/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/file/file.dpi)\nlib/$BINNAME/dpi/zip/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/zip/zip.dpi)\nlib/$BINNAME/dpi/man/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/man/man.dpi)\nlib/$BINNAME/dpi/ftp/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/ftp/ftp.filter.dpi)\nlib/$BINNAME/dpi/gemini/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/gemini/gemini.filter.dpi)\nlib/$BINNAME/dpi/gopher/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/gopher/gopher.filter.dpi)\nlib/$BINNAME/dpi/hello/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/hello/hello.filter.dpi)\nlib/$BINNAME/dpi/vsource/\n@bin $(gen_file \"/usr/local/\" lib/$BINNAME/dpi/vsource/vsource.filter.dpi)\n@man $(gen_file \"/usr/local/\" man/man1/$BINNAME.1)\nshare/doc/$BINNAME/\n$(gen_file \"/usr/local/\" share/doc/$BINNAME/user_help.html)\n$(gen_file \"/usr/local/\" share/doc/$BINNAME/Cookies.txt)\netc/$BINNAME/\n$(gen_file \"/usr/local/\" etc/$BINNAME/dpidrc)\n$(gen_file \"/usr/local/\" etc/$BINNAME/dillorc)\n$(gen_file \"/usr/local/\" etc/$BINNAME/bm.txt)\n$(gen_file \"/usr/local/\" etc/$BINNAME/style.css)\n$(gen_file \"/usr/local/\" etc/$BINNAME/style_reader_mode.css)\nEOF\n\n"
  },
  {
    "path": "src/IO/IO.c",
    "content": "/*\n * File: IO.c\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Dillo's event driven IO engine\n */\n\n#include <errno.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include \"../msg.h\"\n#include \"../chain.h\"\n#include \"../klist.h\"\n#include \"IO.h\"\n#include \"iowatch.hh\"\n#include \"tls.h\"\n\n/*\n * Symbolic defines for shutdown() function\n * (Not defined in the same header file, for all distros --Jcid)\n */\n#define IO_StopRd   1\n#define IO_StopWr   2\n#define IO_StopRdWr (IO_StopRd | IO_StopWr)\n\n\ntypedef struct {\n   int Key;               /* Primary Key (for klist) */\n   int Op;                /* IORead | IOWrite */\n   int FD;                /* Current File Descriptor */\n   int Status;            /* nonzero upon IO failure */\n   Dstr *Buf;             /* Internal buffer */\n\n   void *Info;            /* CCC Info structure for this IO */\n} IOData_t;\n\n\n/*\n * Local data\n */\nstatic Klist_t *ValidIOs = NULL; /* Active IOs list. It holds pointers to\n                                  * IOData_t structures. */\n\n/*\n *  Forward declarations\n */\nvoid a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,\n              void *Data1, void *Data2);\n\n\n/* IO API  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n\n/*\n * Return a new, initialized, 'io' struct\n */\nstatic IOData_t *IO_new(int op)\n{\n   IOData_t *io = dNew0(IOData_t, 1);\n   io->Op = op;\n   io->FD = -1;\n   io->Key = 0;\n   io->Buf = dStr_sized_new(IOBufLen);\n\n   return io;\n}\n\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n\n/*\n * Register an IO in ValidIOs\n */\nstatic void IO_ins(IOData_t *io)\n{\n   if (io->Key == 0) {\n      io->Key = a_Klist_insert(&ValidIOs, io);\n   }\n   _MSG(\"IO_ins: io->Key=%d, Klist_length=%d\\n\",\n       io->Key, a_Klist_length(ValidIOs));\n}\n\n/*\n * Remove an IO from ValidIOs\n */\nstatic void IO_del(IOData_t *io)\n{\n   if (io->Key != 0) {\n      a_Klist_remove(ValidIOs, io->Key);\n   }\n   io->Key = 0;\n   _MSG(\" -->ValidIOs: %d\\n\", a_Klist_length(ValidIOs));\n}\n\n/*\n * Return a io by its Key (NULL if not found)\n */\nstatic IOData_t *IO_get(int Key)\n{\n   return (IOData_t *)a_Klist_get_data(ValidIOs, Key);\n}\n\n/*\n * Free an 'io' struct\n */\nstatic void IO_free(IOData_t *io)\n{\n   dStr_free(io->Buf, 1);\n   dFree(io);\n}\n\n/*\n * Close an open FD, and remove io controls.\n * (This function can be used for Close and Abort operations)\n * BUG: there's a race condition for Abort. The file descriptor is closed\n * twice, and it could be reused for something else in between. It's simple\n * to fix, but it'd be better to design a canonical way to Abort the CCC.\n */\nstatic void IO_close_fd(IOData_t *io, int CloseCode)\n{\n   int events = 0;\n\n   _MSG(\"====> begin IO_close_fd (%d) Key=%d CloseCode=%d \",\n       io->FD, io->Key, CloseCode);\n\n   /* With HTTP, if we close the writing part, the reading one also gets\n    * closed! */\n   if ((CloseCode == IO_StopRdWr) && io->FD != -1) {\n      dClose(io->FD);\n   } else {\n      _MSG(\" NOT CLOSING \");\n   }\n   /* Remove this IOData_t reference, from our ValidIOs list\n    * We don't deallocate it here, just remove from the list.*/\n   IO_del(io);\n\n   /* Stop the polling on this FD */\n   if (CloseCode & IO_StopRd) {\n     events |= DIO_READ;\n   }\n   if (CloseCode & IO_StopWr) {\n     events |= DIO_WRITE;\n   }\n   a_IOwatch_remove_fd(io->FD, events);\n   _MSG(\" end IO close (%d) <=====\\n\", io->FD);\n}\n\n/*\n * Read data from a file descriptor into a specific buffer\n */\nstatic bool_t IO_read(IOData_t *io)\n{\n   char Buf[IOBufLen];\n   ssize_t St;\n   bool_t ret = FALSE;\n   int io_key = io->Key;\n   void *conn = a_Tls_connection(io->FD);\n\n   _MSG(\"  IO_read\\n\");\n\n   /* this is a new read-buffer */\n   dStr_truncate(io->Buf, 0);\n   io->Status = 0;\n\n   while (1) {\n      St = conn ? a_Tls_read(conn, Buf, IOBufLen)\n                : read(io->FD, Buf, IOBufLen);\n      if (St > 0) {\n         dStr_append_l(io->Buf, Buf, St);\n         continue;\n      } else if (St < 0) {\n         if (errno == EINTR) {\n            continue;\n         } else if (errno == EAGAIN) {\n            ret = TRUE;\n            break;\n         } else {\n            if (conn) {\n               io->Status = St;\n               break;\n            } else {\n               io->Status = errno;\n               MSG(\"READ Failed with %d: %s\\n\", (int)St, strerror(errno));\n               break;\n            }\n         }\n      } else { /* St == 0 */\n         break;\n      }\n   }\n\n   if (io->Buf->len > 0) {\n      /* send what we've got so far */\n      a_IO_ccc(OpSend, 2, FWD, io->Info, io, NULL);\n   }\n   if (St == 0) {\n      /* TODO: design a general way to avoid reentrancy problems with CCC. */\n\n      /* The following check is necessary because the above CCC operation\n       * may abort the whole Chain. */\n      if ((io = IO_get(io_key))) {\n         /* All data read (EOF) */\n         _MSG(\"IO_read: io->Key=%d io_key=%d\\n\", io->Key, io_key);\n         a_IO_ccc(OpEnd, 2, FWD, io->Info, io, NULL);\n      }\n   }\n   return ret;\n}\n\n/*\n * Write data, from a specific buffer, into a file descriptor\n */\nstatic bool_t IO_write(IOData_t *io)\n{\n   ssize_t St;\n   bool_t ret = FALSE;\n   void *conn = a_Tls_connection(io->FD);\n\n   _MSG(\"  IO_write\\n\");\n   io->Status = 0;\n\n   while (1) {\n      St = conn ? a_Tls_write(conn, io->Buf->str, io->Buf->len)\n                : write(io->FD, io->Buf->str, io->Buf->len);\n      if (St < 0) {\n         /* Error */\n         if (errno == EINTR) {\n            continue;\n         } else if (errno == EAGAIN) {\n            ret = TRUE;\n            break;\n         } else {\n            if (conn) {\n               io->Status = St;\n               break;\n            } else {\n               io->Status = errno;\n               MSG(\"WRITE Failed with %d: %s\\n\", (int)St, strerror(errno));\n               break;\n            }\n         }\n      } else if (St < io->Buf->len) {\n         /* Not all data written */\n         dStr_erase (io->Buf, 0, St);\n      } else {\n         /* All data in buffer written */\n         dStr_truncate(io->Buf, 0);\n         break;\n      }\n   }\n\n   return ret;\n}\n\n/*\n * Handle background IO for a given FD (reads | writes)\n * (This function gets called when there's activity in the FD)\n */\nstatic int IO_callback(IOData_t *io)\n{\n   bool_t ret = FALSE;\n\n   _MSG(\"IO_callback:: (%s) FD = %d\\n\",\n        (io->Op == IORead) ? \"IORead\" : \"IOWrite\", io->FD);\n\n   if (io->Op == IORead) {          /* Read */\n      ret = IO_read(io);\n   } else if (io->Op == IOWrite) {  /* Write */\n      ret = IO_write(io);\n   }\n   return (ret) ? 1 : 0;\n}\n\n/*\n * Handle the READ event of a FD.\n */\nstatic void IO_fd_read_cb(int fd, void *data)\n{\n   int io_key = VOIDP2INT(data);\n   IOData_t *io = IO_get(io_key);\n\n   /* There should be no more events on already closed FDs  --Jcid */\n   if (io == NULL) {\n      MSG_ERR(\"IO_fd_read_cb: call on already closed io!\\n\");\n      a_IOwatch_remove_fd(fd, DIO_READ);\n\n   } else {\n      if (IO_callback(io) == 0)\n         a_IOwatch_remove_fd(fd, DIO_READ);\n      if ((io = IO_get(io_key)) && io->Status) {\n         /* check io because IO_read OpSend could trigger abort */\n         a_IO_ccc(OpAbort, 2, FWD, io->Info, io, NULL);\n      }\n   }\n}\n\n/*\n * Handle the WRITE event of a FD.\n */\nstatic void IO_fd_write_cb(int fd, void *data)\n{\n   int io_key = VOIDP2INT(data);\n   IOData_t *io = IO_get(io_key);\n\n   if (io == NULL) {\n      /* There must be no more events on already closed FDs  --Jcid */\n      MSG_ERR(\"IO_fd_write_cb: call on already closed io!\\n\");\n      a_IOwatch_remove_fd(fd, DIO_WRITE);\n\n   } else {\n      if (IO_callback(io) == 0)\n         a_IOwatch_remove_fd(fd, DIO_WRITE);\n      if (io->Status)\n         a_IO_ccc(OpAbort, 1, FWD, io->Info, NULL, NULL);\n   }\n}\n\n/*\n * Receive an IO request (IORead | IOWrite),\n * Set a watch for it, and let it flow!\n */\nstatic void IO_submit(IOData_t *r_io)\n{\n   if (r_io->FD < 0) {\n      MSG_ERR(\"IO_submit: FD not initialized\\n\");\n      return;\n   }\n\n   /* Insert this IO in ValidIOs */\n   IO_ins(r_io);\n\n   _MSG(\"IO_submit:: (%s) FD = %d\\n\",\n        (r_io->Op == IORead) ? \"IORead\" : \"IOWrite\", r_io->FD);\n\n   /* Set FD to background and to close on exec. */\n   fcntl(r_io->FD, F_SETFL, O_NONBLOCK | fcntl(r_io->FD, F_GETFL));\n   fcntl(r_io->FD, F_SETFD, FD_CLOEXEC | fcntl(r_io->FD, F_GETFD));\n\n   if (r_io->Op == IORead) {\n      a_IOwatch_add_fd(r_io->FD, DIO_READ,\n                       IO_fd_read_cb, INT2VOIDP(r_io->Key));\n\n   } else if (r_io->Op == IOWrite) {\n      a_IOwatch_add_fd(r_io->FD, DIO_WRITE,\n                       IO_fd_write_cb, INT2VOIDP(r_io->Key));\n   }\n}\n\n/*\n * CCC function for the IO module\n * ( Data1 = IOData_t* ; Data2 = NULL )\n */\nvoid a_IO_ccc(int Op, int Branch, int Dir, ChainLink *Info,\n              void *Data1, void *Data2)\n{\n   IOData_t *io;\n   DataBuf *dbuf;\n\n   dReturn_if_fail( a_Chain_check(\"a_IO_ccc\", Op, Branch, Dir, Info) );\n\n   if (Branch == 1) {\n      if (Dir == BCK) {\n         /* Write data using select */\n         switch (Op) {\n         case OpStart:\n            io = IO_new(IOWrite);\n            io->Info = Info;\n            Info->LocalKey = io;\n            break;\n         case OpSend:\n            io = Info->LocalKey;\n            if (Data2 && !strcmp(Data2, \"FD\")) {\n               io->FD = *(int*)Data1; /* SockFD */\n            } else {\n               dbuf = Data1;\n               dStr_append_l(io->Buf, dbuf->Buf, dbuf->Size);\n               IO_submit(io);\n            }\n            break;\n         case OpEnd:\n         case OpAbort:\n            io = Info->LocalKey;\n            if (io->Buf->len > 0) {\n               char *newline = memchr(io->Buf->str, '\\n', io->Buf->len);\n               int msglen = newline ? newline - io->Buf->str : 2048;\n\n               MSG(\"IO_write, closing with pending data not sent: \\\"%s\\\"\\n\",\n                   dStr_printable(io->Buf, msglen));\n            }\n            /* close FD, remove from ValidIOs and remove its watch */\n            IO_close_fd(io, Op == OpEnd ? IO_StopWr : IO_StopRdWr);\n            IO_free(io);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC IO 1B\\n\");\n            break;\n         }\n      } else {  /* 1 FWD */\n         /* Write-data status */\n         switch (Op) {\n         case OpAbort:\n            io = Info->LocalKey;\n            IO_close_fd(io, IO_StopRdWr);\n            IO_free(io);\n            a_Chain_fcb(OpAbort, Info, NULL, NULL);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC IO 1F\\n\");\n            break;\n         }\n      }\n\n   } else if (Branch == 2) {\n      if (Dir == BCK) {\n         /* This part catches the reader's messages */\n         switch (Op) {\n         case OpStart:\n            io = IO_new(IORead);\n            Info->LocalKey = io;\n            io->Info = Info;\n            break;\n         case OpSend:\n            io = Info->LocalKey;\n            if (Data2 && !strcmp(Data2, \"FD\")) {\n               io->FD = *(int*)Data1; /* SockFD */\n               IO_submit(io);\n            }\n            break;\n         case OpEnd:\n         case OpAbort:\n            io = Info->LocalKey;\n            IO_close_fd(io, Op == OpEnd ? IO_StopRd : IO_StopRdWr);\n            IO_free(io);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC IO 2B\\n\");\n            break;\n         }\n      } else {  /* 2 FWD */\n         /* Send read-data */\n         io = Data1;\n         switch (Op) {\n         case OpSend:\n            dbuf = a_Chain_dbuf_new(io->Buf->str, io->Buf->len, 0);\n            a_Chain_fcb(OpSend, Info, dbuf, NULL);\n            dFree(dbuf);\n            break;\n         case OpEnd:\n         case OpAbort:\n            a_Chain_fcb(Op, Info, NULL, NULL);\n            IO_close_fd(io, IO_StopRdWr); /* IO_StopRd would leak FDs */\n            IO_free(io);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC IO 2F\\n\");\n            break;\n         }\n      }\n   }\n}\n\n"
  },
  {
    "path": "src/IO/IO.h",
    "content": "#ifndef __IO_H__\n#define __IO_H__\n\n#include \"d_size.h\"\n#include \"../../dlib/dlib.h\"\n#include \"../chain.h\"\n\n/*\n * IO Operations\n */\n#define IORead   0\n#define IOWrite  1\n#define IOClose  2\n#define IOAbort  3\n\n/*\n * IO constants\n */\n#define IOBufLen         8192\n\n\n/*\n * Exported functions\n */\n/* Note: a_IO_ccc() is defined in Url.h together with the *_ccc() set */\n\n\n/*\n * Exported data\n */\nextern const char *AboutSplash;\n\n\n#endif /* __IO_H__ */\n\n"
  },
  {
    "path": "src/IO/Makefile",
    "content": "include ../../Makefile.options\n\nINCLUDES_EXTRA = -I../..\nCXXFLAGS_EXTRA = -DDILLO_BINDIR='\"$(DILLO_BINDIR)\"' -DCA_CERTS_FILE='\"$(CA_CERTS_FILE)\"' -DCA_CERTS_DIR='\"$(CA_CERTS_DIR)\"' -DDOC_PATH='\"$(DOC_PATH)\"' -DBINNAME='\"$(BINNAME)\"'\n\nall: libDiof.a\n\nlibDiof.a: mime.o about.o http.o dpi.o IO.o iowatch.o proto.o tls.o\n\t$(AR) $(ARFLAGS) libDiof.a mime.o about.o http.o dpi.o IO.o iowatch.o proto.o tls.o\n\t$(RANLIB) libDiof.a\n\nmime.o: mime.c mime.h\n\t$(COMPILE) $(INCLUDES_EXTRA) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) -c mime.c\n\nabout.o: about.c ../../config.h\n\t$(COMPILE) $(INCLUDES_EXTRA) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) -c about.c\n\nhttp.o: http.c\n\t$(COMPILE) $(INCLUDES_EXTRA) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) -c http.c\n\nproto.o: proto.c\n\t$(COMPILE) $(INCLUDES_EXTRA) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) -c proto.c\n\ntls.o: tls.c\n\t$(COMPILE) $(INCLUDES_EXTRA) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) -c tls.c\n\ndpi.o: dpi.c\n\t$(COMPILE) $(INCLUDES_EXTRA) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) -c dpi.c\n\nIO.o: IO.c IO.h\n\t$(COMPILE) $(INCLUDES_EXTRA) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) -c IO.c\n\niowatch.o: iowatch.cc iowatch.hh\n\t$(CXXCOMPILE) $(INCLUDES_EXTRA) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) -c iowatch.cc\n\nclean:\n\trm -f *.o *.a\n\ninstall:\n"
  },
  {
    "path": "src/IO/Url.h",
    "content": "#ifndef __IO_URL_H__\n#define __IO_URL_H__\n\n#include \"../chain.h\"\n#include \"../url.h\"\n#include \"../../dlib/dlib.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n * External functions\n */\nextern void a_Http_freeall(void);\nint a_Http_init(void);\nint a_Http_proxy_auth(void);\nvoid a_Http_set_proxy_passwd(const char *str);\nvoid a_Http_connect_done(int fd, bool_t success);\n\nvoid a_Http_ccc (int Op, int Branch, int Dir, ChainLink *Info,\n                 void *Data1, void *Data2);\nvoid a_IO_ccc   (int Op, int Branch, int Dir, ChainLink *Info,\n                 void *Data1, void *Data2);\nvoid a_Dpi_ccc  (int Op, int Branch, int Dir, ChainLink *Info,\n                 void *Data1, void *Data2);\n\nchar *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd);\nvoid a_Dpi_dillo_exit(void);\nvoid a_Dpi_init(void);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __IO_URL_H__ */\n\n"
  },
  {
    "path": "src/IO/about.c",
    "content": "/*\n * File: about.c\n *\n * Copyright (C) 1999-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <config.h>\n\n/*\n * HTML text for startup screen\n */\nconst char *const AboutSplash=\n\"<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\\n\"\n\"<html>\\n\"\n\"<head>\\n\"\n\"<title>Splash screen for dillo-\" VERSION \"</title>\\n\"\n\"<style>\\n\"\n\"\tbody {\\n\"\n\"\t\tbackground-color: #778899;\\n\"\n\"\t\tcolor: #0f0f0f;\\n\"\n\"\t}\\n\"\n\"\th1 {\\n\"\n\"\t\tcolor: white;\\n\"\n\"\t\tmargin: 0 1em 0 1em;\\n\"\n\"\t\ttext-align: center;\\n\"\n\"\t}\\n\"\n\"\tth {\\n\"\n\"\t\tcolor: white;\\n\"\n\"\t}\\n\"\n\"</style>\\n\"\n\"</head>\\n\"\n\"\\n\"\n\"<body>\\n\"\n\"\\n\"\n\"<br>\\n\"\n\"\\n\"\n\"<table width='100%' border='0' cellspacing='1' cellpadding='3' bgcolor='#000000'>\\n\"\n\"  <tr><th bgcolor='#4F4F4F'><h1>About Dillo+ \" VERSION \"</h1></th></tr>\\n\"\n\"</table>\\n\"\n\"\\n\"\n\"<br>\\n\"\n\"\\n\"\n\"\\n\"\n\"<!-- the main layout table, definition -->\\n\"\n\"\\n\"\n\"<table width='100%' border='0' cellspacing='0' cellpadding='0'>\\n\"\n\"<tr><td valign='top' width='150' align='center'>\\n\"\n\"\\n\"\n\"\\n\"\n\"<!--   The navigation bar   -->\\n\"\n\"\\n\"\n\"<table border='0' cellspacing='0' cellpadding='0' width='140' bgcolor='#000000'>\\n\"\n\"<tr>\\n\"\n\" <td>\\n\"\n\"  <table width='100%' border='0' cellspacing='1' cellpadding='3'>\\n\"\n\"  <tr>\\n\"\n\"   <th colspan='1' bgcolor='#4F4F4F'><b>Dillo+</b>\\n\"\n\"  <tr>\\n\"\n\"   <td bgcolor='#FFFFFF'>\\n\"\n\"    <table border='0' cellspacing='0' cellpadding='5'><tr><td>\\n\"\n\"    <table border='0' cellspacing='0' cellpadding='2'><tr>\\n\"\n\"    <td><td><a href='file:\" DOC_PATH \"/user_help.html'>Help</a>\\n\"\n\"     <tr><td><td><a href='https://dillo-browser.github.io/old/dillo3-help.html'>\\n\"\n\"     Online Help</a>\\n\"\n\"    <tr>\\n\"\n\"    <td>&nbsp;&nbsp;\\n\"\n\"    <td>\\n\"\n\"     <a href='https://github.com/crossbowerbt/dillo-plus/'>Project Home</a>\\n\"\n\"    </table>\\n\"\n\"    </table>\\n\"\n\"  </table>\\n\"\n\"</table>\\n\"\n\"\\n\"\n\"<br>\\n\"\n\"\\n\"\n\"<table border='0' width='100%' cellpadding='10' cellspacing='0'><tr><td height='10'></table>\\n\"\n\"\\n\"\n\"\\n\"\n\"<!-- the main layout table, a small vertical spacer -->\\n\"\n\"\\n\"\n\"<td width='20'><td valign='top'>\\n\"\n\"\\n\"\n\"\\n\"\n\"<!--   Main Part of the page   -->\\n\"\n\"\\n\"\n\"<table border='0' cellpadding='0' cellspacing='0' align='center' bgcolor='#000000' width='100%'><tr><td>\\n\"\n\"<table border='0' cellpadding='5' cellspacing='1' width='100%'>\\n\"\n\"<tr ALIGN=LEFT VALIGN=TOP>\\n\"\n\" <td bgcolor='lightgrey' color='black'>\\n\"\n\"  <h4>Release Notes</h4>\\n\"\n\"<tr>\\n\"\n\" <td bgcolor='#FFFFFF'>\\n\"\n\"  <table border='0' cellspacing='0' cellpadding='5'><tr><td>\\n\"\n\"  <p>\\n\"\n\"  <b>Dillo+</b> is a graphical web browser derived from <i>Dillo</i>. The browser focuses on accessing lightweight websites and, for more common websites, it limits loading of bload and advertisements.  For both speed and security, there's no support for Javascript</p>\\n\"\n\" <h4>Protocols:</h4>\\n\"\n\" <p>\\n\"\n\"  Dillo+ supports the lightweight protocols gopher and gemini in addition to http and https. It aims to become your default browser for accessing the <i>smol</i> web.</p>\\n\"\n\" <h4>Reader mode CSS:</h4>\\n\"\n\" <p>To make it easier reading articles from the web, and also to rearrange large pages in a format easier on the eyes, it is possible to activate a reader mode CSS from the 'Tools' menu.</p>\\n\"\n\" <h4>Additional content types:</h4>\\n\"\n\" <p>Support for rendering pages and local files written in the gemini, gopher, markdown and rss formats has been added.</p>\\n\"\n\" <h4>Additional DPIs:</h4>\\n\"\n\" <p>To implement some advanced features, like <i>epubs</i> and <i>manpages</i> reading, a serie of additional DPI modules has been added by default to the browser.</p>\\n\"\n\" <p>The current list of DPIs is the following:</p>\\n\"\n\" <ul>\\n\"\n\"        <li><a href=\\\"file://\\\">file://</a> file browser able to render files according to their type (some improvements on the original dillo DPI have been made)</li>\\n\"\n\"        <li><a href=\\\"zip://\\\">zip://</a> file browser for zip archives able to render compressed files according to their type, including HTML files (and their links and referenced resources) to easily visualize EPub ebooks</li>\\n\"\n\"        <li><a href=\\\"man://\\\">man://</a> man page viewer with man links support, very handy on a *nix system to quickly navigate the docs</li>\\n\"\n\"        <li><a href=\\\"ftp://\\\">ftp://</a> file browser for the FTP protocol (supports also the OpenBSD ftp client and not only wget)</li>\\n\"\n\"        <li><a href=\\\"gemini://\\\">gemini://</a> browser for the Gemini protocol</li>\\n\"\n\"        <li><a href=\\\"gopher://\\\">gopher://</a> browser for the Gopher protocol</li>\\n\"\n\" </ul>\\n\"\n\"<h4>Searching:</h4>\\n\"\n\"<p>\\n\"\n\"If wanted, there is a search dialog that is available by pressing 's'.  It includes your search engines\\n\"\n\"that are defined in your dillorc file\\n\"\n\"</p>\\n\"\n\" <h5>Quick Searching</h5>\\n\"\n\" <p>You can search common sites by typing one of the following key letters, followed by a space and your search term.</p>\\n\"\n\" <p>For example, <i>'dd pizza'</i>, will search the Duck Duck Go website for the term <i>'pizza'</i>.</p>\\n\"\n\" <ul>\\n\"\n\"        <li>dd - to search DuckDuckGo</li>\\n\"\n\"        <li>se - to search SearX (prvcy.eu)</li>\\n\"\n\"        <li>se2 - to search2 SearX (metasearx.com)</li>\\n\"\n\"        <li>se3 - to search3 SearX (searxng.au)</li>\\n\"\n\"        <li>se4 - to search4 SearX (cosmohub.io)</li>\\n\"\n\"        <li>ya - to search Yandex</li>\\n\"\n\"        <li>wk - to search Wikipedia</li>\\n\"\n\"        <li>ar - to search Internet Archive</li>\\n\"\n\"        <li>fd - to search Free Dictionary</li>\\n\"\n\"        <li>sp - to search Startpage</li>\\n\"\n\"        <li>mn - to search Marginalia</li>\\n\"\n\"        <li>te - to search Teclis</li>\\n\"\n\"        <li>gg - to search Google</li>\\n\"\n\"        <li>ge - to search GeminiSpace</li>\\n\"\n\"        <li>lg - to search LibGen</li>\\n\"\n\"        <li>gr - to search GoodReads</li>\\n\"\n\"        <li>pb - to search The Pirate Bay</li>\\n\"\n\"        <li>tw - to search Twitter</li>\\n\"\n\"        <li>un - to search Unsplash</li>\\n\"\n\"        <li>dv - to search Deviant Art</li>\\n\"\n\" </ul>\\n\"\n\" <p>To modify these quick searches, refer to this <a href='file:\" DOC_PATH \"/user_help.html#search'>link</a>.</p>\\n\"\n\" <br/>\"\n\" <p><b>Please, enjoy using Dillo+!</b><p>\\n\"\n\"  </table>\\n\"\n\"</table>\\n\"\n\"</table>\\n\"\n\"\\n\"\n\"<!--   footnotes   -->\\n\"\n\"\\n\"\n\"<br><br><center>\\n\"\n\"</center>\\n\"\n\"</body>\\n\"\n\"</html>\\n\";\n\n"
  },
  {
    "path": "src/IO/dpi.c",
    "content": "/*\n * File: dpi.c\n *\n * Copyright (C) 2002-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Dillo plugins (small programs that interact with dillo)\n *\n * Dillo plugins are designed to handle:\n *   bookmarks, cookies, FTP, downloads, files, preferences, https,\n *   datauri and a lot of any-to-html filters.\n */\n\n\n#include <unistd.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <string.h>\n#include <stdio.h>\n#include <errno.h>           /* for errno */\n#include <fcntl.h>\n#include <ctype.h>           /* isxdigit */\n\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <netinet/in.h>\n#include <netinet/tcp.h>\n#include <arpa/inet.h>\n#include <netdb.h>\n\n#include \"../msg.h\"\n#include \"../klist.h\"\n#include \"IO.h\"\n#include \"Url.h\"\n#include \"../../dpip/dpip.h\"\n\n/* This one is tricky, some sources state it should include the byte\n * for the terminating NULL, and others say it shouldn't. */\n# define D_SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \\\n                        + strlen ((ptr)->sun_path))\n\n/* Solaris may not have this one... */\n#ifndef AF_LOCAL\n#define AF_LOCAL AF_UNIX\n#endif\n\n\ntypedef struct {\n   int InTag;\n   int Send2EOF;\n\n   int DataTotalSize;\n   int DataRecvSize;\n\n   Dstr *Buf;\n\n   int BufIdx;\n   int TokIdx;\n   int TokSize;\n   int TokIsTag;\n\n   ChainLink *InfoRecv;\n   int Key;\n} dpi_conn_t;\n\n\n/*\n * Local data\n */\nstatic Klist_t *ValidConns = NULL; /* Active connections list. It holds\n                                    * pointers to dpi_conn_t structures. */\nstatic char SharedKey[32];\n\n/*\n * Initialize local data\n */\nvoid a_Dpi_init(void)\n{\n   /* empty */\n}\n\n/*\n * Create a new connection data structure\n */\nstatic dpi_conn_t *Dpi_conn_new(ChainLink *Info)\n{\n   dpi_conn_t *conn = dNew0(dpi_conn_t, 1);\n\n   conn->Buf = dStr_sized_new(8*1024);\n   conn->InfoRecv = Info;\n   conn->Key = a_Klist_insert(&ValidConns, conn);\n\n   return conn;\n}\n\n/*\n * Free a connection data structure\n */\nstatic void Dpi_conn_free(dpi_conn_t *conn)\n{\n   a_Klist_remove(ValidConns, conn->Key);\n   dStr_free(conn->Buf, 1);\n   dFree(conn);\n}\n\n/*\n * Check whether a conn is still valid.\n * Return: 1 if found, 0 otherwise\n */\nstatic int Dpi_conn_valid(int key)\n{\n   return (a_Klist_get_data(ValidConns, key)) ? 1 : 0;\n}\n\n/*\n * Append the new buffer in 'dbuf' to Buf in 'conn'\n */\nstatic void Dpi_append_dbuf(dpi_conn_t *conn, DataBuf *dbuf)\n{\n   if (dbuf->Code == 0 && dbuf->Size > 0) {\n      dStr_append_l(conn->Buf, dbuf->Buf, dbuf->Size);\n   }\n}\n\n/*\n * Split the data stream into tokens.\n * Here, a token is either:\n *    a) a dpi tag\n *    b) a raw data chunk\n *\n * Return Value: 0 upon a new token, -1 on not enough data.\n *\n * TODO: define an API and move this function into libDpip.a.\n*/\nstatic int Dpi_get_token(dpi_conn_t *conn)\n{\n   int i, resp = -1;\n   char *buf = conn->Buf->str;\n\n   if (conn->BufIdx == conn->Buf->len) {\n      dStr_truncate(conn->Buf, 0);\n      conn->BufIdx = 0;\n      return resp;\n   }\n\n   if (conn->Send2EOF) {\n      conn->TokIdx = conn->BufIdx;\n      conn->TokSize = conn->Buf->len - conn->BufIdx;\n      conn->BufIdx = conn->Buf->len;\n      return 0;\n   }\n\n   _MSG(\"conn->BufIdx = %d; conn->Buf->len = %d\\nbuf: [%s]\\n\",\n        conn->BufIdx,conn->Buf->len, conn->Buf->str + conn->BufIdx);\n\n   if (!conn->InTag) {\n      /* search for start of tag */\n      while (conn->BufIdx < conn->Buf->len && buf[conn->BufIdx] != '<')\n         ++conn->BufIdx;\n      if (conn->BufIdx < conn->Buf->len) {\n         /* found */\n         conn->InTag = 1;\n         conn->TokIdx = conn->BufIdx;\n      } else {\n         MSG_ERR(\"[Dpi_get_token] Can't find token start\\n\");\n      }\n   }\n\n   if (conn->InTag) {\n      /* search for end of tag (EOT=\" '>\") */\n      for (i = conn->BufIdx; i < conn->Buf->len; ++i)\n         if (buf[i] == '>' && i >= 2 && buf[i-1] == '\\'' && buf[i-2] == ' ')\n            break;\n      conn->BufIdx = i;\n\n      if (conn->BufIdx < conn->Buf->len) {\n         /* found EOT */\n         conn->TokIsTag = 1;\n         conn->TokSize = conn->BufIdx - conn->TokIdx + 1;\n         ++conn->BufIdx;\n         conn->InTag = 0;\n         resp = 0;\n      }\n   }\n\n   return resp;\n}\n\n/*\n * Parse a dpi tag and take the appropriate actions\n */\nstatic void Dpi_parse_token(dpi_conn_t *conn)\n{\n   char *tag, *cmd, *msg, *urlstr;\n   DataBuf *dbuf;\n   char *Tok = conn->Buf->str + conn->TokIdx;\n\n   if (conn->Send2EOF) {\n      /* we're receiving data chunks from a HTML page */\n      dbuf = a_Chain_dbuf_new(Tok, conn->TokSize, 0);\n      a_Chain_fcb(OpSend, conn->InfoRecv, dbuf, \"send_page_2eof\");\n      dFree(dbuf);\n      return;\n   }\n\n   tag = dStrndup(Tok, (size_t)conn->TokSize);\n   _MSG(\"Dpi_parse_token: {%s}\\n\", tag);\n\n   cmd = a_Dpip_get_attr_l(Tok, conn->TokSize, \"cmd\");\n   if (strcmp(cmd, \"send_status_message\") == 0) {\n      msg = a_Dpip_get_attr_l(Tok, conn->TokSize, \"msg\");\n      a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd);\n      dFree(msg);\n\n   } else if (strcmp(cmd, \"chat\") == 0) {\n      msg = a_Dpip_get_attr_l(Tok, conn->TokSize, \"msg\");\n      a_Chain_fcb(OpSend, conn->InfoRecv, msg, cmd);\n      dFree(msg);\n\n   } else if (strcmp(cmd, \"dialog\") == 0) {\n      /* For now will send the dpip tag... */\n      a_Chain_fcb(OpSend, conn->InfoRecv, tag, cmd);\n\n   } else if (strcmp(cmd, \"start_send_page\") == 0) {\n      conn->Send2EOF = 1;\n      urlstr = a_Dpip_get_attr_l(Tok, conn->TokSize, \"url\");\n      a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd);\n      dFree(urlstr);\n      /* TODO: a_Dpip_get_attr_l(Tok, conn->TokSize, \"send_mode\") */\n\n   } else if (strcmp(cmd, \"reload_request\") == 0) {\n      urlstr = a_Dpip_get_attr_l(Tok, conn->TokSize, \"url\");\n      a_Chain_fcb(OpSend, conn->InfoRecv, urlstr, cmd);\n      dFree(urlstr);\n   }\n   dFree(cmd);\n\n   dFree(tag);\n}\n\n\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n\n/*\n * Write data into a file descriptor taking care of EINTR\n * and possible data splits.\n * Return value: 1 on success, -1 on error.\n */\nstatic int Dpi_blocking_write(int fd, const char *msg, int msg_len)\n{\n   int st, sent = 0;\n\n   while (sent < msg_len) {\n      st = write(fd, msg + sent, msg_len - sent);\n      if (st < 0) {\n         if (errno == EINTR) {\n            continue;\n         } else {\n            MSG_ERR(\"[Dpi_blocking_write] %s\\n\", dStrerror(errno));\n            break;\n         }\n      }\n      sent += st;\n   }\n\n   return (sent == msg_len) ? 1 : -1;\n}\n\n/*\n * Read all the available data from a filedescriptor.\n * This is intended for short answers, i.e. when we know the server\n * will write it all before being preempted. For answers that may come\n * as an stream with delays, non-blocking is better.\n * Return value: read data, or NULL on error and no data.\n */\nstatic char *Dpi_blocking_read(int fd)\n{\n   int st;\n   const int buf_sz = 8*1024;\n   char buf[buf_sz], *msg = NULL;\n   Dstr *dstr = dStr_sized_new(buf_sz);\n\n   do {\n      st = read(fd, buf, buf_sz);\n      if (st < 0) {\n         if (errno == EINTR) {\n            continue;\n         } else {\n            MSG_ERR(\"[Dpi_blocking_read] %s\\n\", dStrerror(errno));\n            break;\n         }\n      } else if (st > 0) {\n         dStr_append_l(dstr, buf, st);\n      }\n   } while (st == buf_sz);\n\n   msg = (dstr->len > 0) ? dstr->str : NULL;\n   dStr_free(dstr, (dstr->len > 0) ? FALSE : TRUE);\n   return msg;\n}\n\n/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */\n\n/*\n * Get a new data buffer (within a 'dbuf'), save it into local data,\n * split in tokens and parse the contents.\n */\nstatic void Dpi_process_dbuf(int Op, void *Data1, dpi_conn_t *conn)\n{\n   DataBuf *dbuf = Data1;\n   int key = conn->Key;\n\n   /* Very useful for debugging: show the data stream as received. */\n   /* fwrite(dbuf->Buf, dbuf->Size, 1, stdout); */\n\n   if (Op == IORead) {\n      Dpi_append_dbuf(conn, dbuf);\n      /* 'conn' has to be validated because Dpi_parse_token() MAY call abort */\n      while (Dpi_conn_valid(key) && Dpi_get_token(conn) != -1) {\n         Dpi_parse_token(conn);\n      }\n\n   } else if (Op == IOClose) {\n      /* unused */\n   }\n}\n\n/*\n * Start dpid.\n * Return: 0 starting now, 1 Error.\n */\nstatic int Dpi_start_dpid(void)\n{\n   pid_t pid;\n   int st_pipe[2], ret = 1;\n   char *answer;\n\n   /* create a pipe to track our child's status */\n   if (pipe(st_pipe))\n      return 1;\n\n   pid = fork();\n   if (pid == 0) {\n      /* This is the child process.  Execute the command. */\n      char *path1 = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/dpid-plus\", NULL);\n      dClose(st_pipe[0]);\n      if (execl(path1, \"dpid-plus\", (char*)NULL) == -1) {\n         dFree(path1);\n         path1 = dStrconcat(DILLO_BINDIR, \"dpid-plus\", NULL);\n         if (execl(path1, \"dpid-plus\", (char*)NULL) == -1) {\n            dFree(path1);\n            if (execlp(\"dpid-plus\", \"dpid-plus\", (char*)NULL) == -1) {\n               MSG(\"Dpi_start_dpid (child): %s\\n\", dStrerror(errno));\n               if (Dpi_blocking_write(st_pipe[1], \"ERROR\", 5) == -1) {\n                  MSG(\"Dpi_start_dpid (child): can't write to pipe.\\n\");\n               }\n               dClose(st_pipe[1]);\n               _exit (EXIT_FAILURE);\n            }\n         }\n      }\n   } else if (pid < 0) {\n      /* The fork failed.  Report failure.  */\n      MSG(\"Dpi_start_dpid: %s\\n\", dStrerror(errno));\n      /* close the unused pipe */\n      dClose(st_pipe[0]);\n      dClose(st_pipe[1]);\n   } else {\n      /* This is the parent process, check our child status... */\n      dClose(st_pipe[1]);\n      if ((answer = Dpi_blocking_read(st_pipe[0])) != NULL) {\n         MSG(\"Dpi_start_dpid: can't start dpid-plus\\n\");\n         dFree(answer);\n      } else {\n         ret = 0;\n      }\n      dClose(st_pipe[0]);\n   }\n\n   return ret;\n}\n\n/*\n * Read dpid's communication keys from its saved file.\n * Return value: 1 on success, -1 on error.\n */\nstatic int Dpi_read_comm_keys(int *port)\n{\n   FILE *In;\n   char *fname, *rcline = NULL, *tail;\n   int i, ret = -1;\n\n   fname = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/dpid_comm_keys\", NULL);\n   if ((In = fopen(fname, \"r\")) == NULL) {\n      MSG_ERR(\"[Dpi_read_comm_keys] %s\\n\", dStrerror(errno));\n   } else if ((rcline = dGetline(In)) == NULL) {\n      MSG_ERR(\"[Dpi_read_comm_keys] empty file: %s\\n\", fname);\n   } else {\n      *port = strtol(rcline, &tail, 10);\n      for (i = 0; *tail && isxdigit(tail[i+1]); ++i)\n         SharedKey[i] = tail[i+1];\n      SharedKey[i] = 0;\n      ret = 1;\n   }\n   if (In)\n      fclose(In);\n   dFree(rcline);\n   dFree(fname);\n\n   return ret;\n}\n\n/*\n * Return a socket file descriptor\n */\nstatic int Dpi_make_socket_fd()\n{\n   int fd, one = 1, ret = -1;\n\n   if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) {\n      /* avoid delays when sending small pieces of data */\n      setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));\n      ret = fd;\n   }\n   return ret;\n}\n\n/*\n * Make a connection test for a IDS.\n * Return: 1 OK, -1 Not working.\n */\nstatic int Dpi_check_dpid_ids()\n{\n   struct sockaddr_in sin;\n   const socklen_t sin_sz = sizeof(sin);\n   int sock_fd, dpid_port, ret = -1;\n\n   /* socket connection test */\n   memset(&sin, 0, sizeof(sin));\n   sin.sin_family = AF_INET;\n   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n\n   if (Dpi_read_comm_keys(&dpid_port) != -1) {\n      sin.sin_port = htons(dpid_port);\n      if ((sock_fd = Dpi_make_socket_fd()) == -1) {\n         MSG(\"Dpi_check_dpid_ids: sock_fd=%d %s\\n\", sock_fd, dStrerror(errno));\n      } else if (connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) {\n         MSG(\"Dpi_check_dpid_ids: %s\\n\", dStrerror(errno));\n      } else {\n         dClose(sock_fd);\n         ret = 1;\n      }\n   }\n   return ret;\n}\n\n/*\n * Confirm that the dpid is running. If not, start it.\n * Return: 0 running OK, 1 starting (EAGAIN), 2 Error.\n */\nstatic int Dpi_check_dpid(int num_tries)\n{\n   static int starting = 0;\n   int check_st = 1, ret = 2;\n\n   check_st = Dpi_check_dpid_ids();\n   _MSG(\"Dpi_check_dpid: check_st=%d\\n\", check_st);\n\n   if (check_st == 1) {\n      /* connection test with dpi server passed */\n      starting = 0;\n      ret = 0;\n   } else {\n      if (!starting) {\n         /* start dpid */\n         if (Dpi_start_dpid() == 0) {\n            starting = 1;\n            ret = 1;\n         }\n      } else if (++starting < num_tries) {\n         /* starting */\n         ret = 1;\n      } else {\n         /* we waited too much, report an error... */\n         starting = 0;\n      }\n   }\n\n   _MSG(\"Dpi_check_dpid: %s\\n\",\n        (ret == 0) ? \"OK\" : (ret == 1 ? \"EAGAIN\" : \"ERROR\"));\n   return ret;\n}\n\n/*\n * Confirm that the dpid is running. If not, start it.\n * Return: 0 running OK, 2 Error.\n */\nstatic int Dpi_blocking_start_dpid(void)\n{\n   int cst, try = 0,\n       n_tries = 12; /* 3 seconds */\n\n   /* test the dpid, and wait a bit for it to start if necessary */\n   while ((cst = Dpi_check_dpid(n_tries)) == 1) {\n      MSG(\"Dpi_blocking_start_dpid: try %d\\n\", ++try);\n      usleep(250000); /* 1/4 sec */\n   }\n   return cst;\n}\n\n/*\n * Return the dpi server's port number, or -1 on error.\n * (A query is sent to dpid and then its answer parsed)\n * note: as the available servers and/or the dpi socket directory can\n *       change at any time, we'll ask each time. If someday we find\n *       that connecting each time significantly degrades performance,\n *       an optimized approach can be tried.\n */\nstatic int Dpi_get_server_port(const char *server_name)\n{\n   int sock_fd = -1, dpi_port = -1;\n   int dpid_port, ok = 0;\n   struct sockaddr_in sin;\n   char *cmd, *request, *rply = NULL, *port_str;\n   socklen_t sin_sz;\n\n   dReturn_val_if_fail (server_name != NULL, dpi_port);\n   _MSG(\"Dpi_get_server_port: server_name = [%s]\\n\", server_name);\n\n   /* Read dpid's port from saved file */\n   if (Dpi_read_comm_keys(&dpid_port) != -1) {\n      ok = 1;\n   }\n   if (ok) {\n      /* Connect a socket with dpid */\n      ok = 0;\n      sin_sz = sizeof(sin);\n      memset(&sin, 0, sizeof(sin));\n      sin.sin_family = AF_INET;\n      sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n      sin.sin_port = htons(dpid_port);\n      if ((sock_fd = Dpi_make_socket_fd()) == -1 ||\n          connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) {\n         MSG(\"Dpi_get_server_port: %s\\n\", dStrerror(errno));\n      } else {\n         ok = 1;\n      }\n   }\n   if (ok) {\n      /* ask dpid to check the dpi and send its port number back */\n      ok = 0;\n      request = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"check_server\", server_name);\n      _MSG(\"[%s]\\n\", request);\n\n      if (Dpi_blocking_write(sock_fd, request, strlen(request)) == -1) {\n         MSG(\"Dpi_get_server_port: %s\\n\", dStrerror(errno));\n      } else {\n         ok = 1;\n      }\n      dFree(request);\n   }\n   if (ok) {\n      /* Get the reply */\n      ok = 0;\n      if ((rply = Dpi_blocking_read(sock_fd)) == NULL) {\n         MSG(\"Dpi_get_server_port: can't read server port from dpid.\\n\");\n      } else {\n         ok = 1;\n      }\n   }\n   if (ok) {\n      /* Parse reply */\n      ok = 0;\n      cmd = a_Dpip_get_attr(rply, \"cmd\");\n      if (strcmp(cmd, \"send_data\") == 0) {\n         port_str = a_Dpip_get_attr(rply, \"msg\");\n         _MSG(\"Dpi_get_server_port: rply=%s\\n\", rply);\n         _MSG(\"Dpi_get_server_port: port_str=%s\\n\", port_str);\n         dpi_port = strtol(port_str, NULL, 10);\n         dFree(port_str);\n         ok = 1;\n      }\n      dFree(cmd);\n   }\n   dFree(rply);\n   dClose(sock_fd);\n\n   return ok ? dpi_port : -1;\n}\n\n\n/*\n * Connect a socket to a dpi server and return the socket's FD.\n * We have to ask 'dpid' (dpi daemon) for the port of the target dpi server.\n * Once we have it, then the proper file descriptor is returned (-1 on error).\n */\nstatic int Dpi_connect_socket(const char *server_name)\n{\n   struct sockaddr_in sin;\n   int sock_fd, dpi_port, ret = -1;\n   char *cmd = NULL;\n\n   /* Query dpid for the port number for this server */\n   if ((dpi_port = Dpi_get_server_port(server_name)) == -1) {\n      _MSG(\"Dpi_connect_socket: can't get port number for %s\\n\", server_name);\n      return -1;\n   }\n   _MSG(\"Dpi_connect_socket: server=%s port=%d\\n\", server_name, dpi_port);\n\n   /* connect with this server's socket */\n   memset(&sin, 0, sizeof(sin));\n   sin.sin_family = AF_INET;\n   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n   sin.sin_port = htons(dpi_port);\n\n   if ((sock_fd = Dpi_make_socket_fd()) == -1) {\n      MSG_ERR(\"[Dpi_connect_socket] %s\\n\", dStrerror(errno));\n   } else if (connect(sock_fd, (void*)&sin, sizeof(sin)) == -1) {\n      MSG_ERR(\"[Dpi_connect_socket] errno:%d %s\\n\", errno, dStrerror(errno));\n\n   /* send authentication Key (the server closes sock_fd on auth error) */\n   } else if (!(cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"auth\", SharedKey))) {\n      MSG_ERR(\"[Dpi_connect_socket] Can't make auth message.\\n\");\n   } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) {\n      MSG_ERR(\"[Dpi_connect_socket] Can't send auth message.\\n\");\n   } else {\n      ret = sock_fd;\n   }\n   dFree(cmd);\n   if (sock_fd != -1 && ret == -1) /* can't send cmd? */\n      dClose(sock_fd);\n\n   return ret;\n}\n\n/*\n * CCC function for the Dpi module\n */\nvoid a_Dpi_ccc(int Op, int Branch, int Dir, ChainLink *Info,\n               void *Data1, void *Data2)\n{\n   dpi_conn_t *conn;\n   int SockFD = -1, st;\n\n   dReturn_if_fail( a_Chain_check(\"a_Dpi_ccc\", Op, Branch, Dir, Info) );\n\n   if (Branch == 1) {\n      if (Dir == BCK) {\n         /* Send commands to dpi-server */\n         switch (Op) {\n         case OpStart:\n            if ((st = Dpi_blocking_start_dpid()) == 0) {\n               if ((SockFD = Dpi_connect_socket(Data1)) != -1) {\n                  int *fd = dNew(int, 1);\n                  *fd = SockFD;\n                  Info->LocalKey = fd;\n                  a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 1, 1);\n                  a_Chain_bcb(OpStart, Info, NULL, NULL);\n                  /* Let the FD known and tracked */\n                  a_Chain_bcb(OpSend, Info, &SockFD, \"FD\");\n                  a_Chain_fcb(OpSend, Info, &SockFD, \"FD\");\n                  a_Chain_fcb(OpSend, Info, NULL, \"DpidOK\");\n               } else {\n                  a_Dpi_ccc(OpAbort, 1, FWD, Info, NULL, NULL);\n               }\n            } else {\n               MSG_ERR(\"dpi.c: can't start dpi daemon\\n\");\n               a_Dpi_ccc(OpAbort, 1, FWD, Info, NULL, \"DpidERROR\");\n            }\n            break;\n         case OpSend:\n            a_Chain_bcb(OpSend, Info, Data1, NULL);\n            break;\n         case OpEnd:\n            a_Chain_bcb(OpEnd, Info, NULL, NULL);\n            dFree(Info->LocalKey);\n            dFree(Info);\n            break;\n         case OpAbort:\n            a_Chain_bcb(OpAbort, Info, NULL, NULL);\n            dFree(Info->LocalKey);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC\\n\");\n            break;\n         }\n      } else {  /* 1 FWD */\n         /* Send commands to dpi-server (status) */\n         switch (Op) {\n         case OpAbort:\n            a_Chain_fcb(OpAbort, Info, NULL, Data2);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC\\n\");\n            break;\n         }\n      }\n\n   } else if (Branch == 2) {\n      if (Dir == FWD) {\n         /* Receiving from server */\n         switch (Op) {\n         case OpSend:\n            /* Data1 = dbuf */\n            Dpi_process_dbuf(IORead, Data1, Info->LocalKey);\n            break;\n         case OpEnd:\n            a_Chain_fcb(OpEnd, Info, NULL, NULL);\n            Dpi_conn_free(Info->LocalKey);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC\\n\");\n            break;\n         }\n      } else {  /* 2 BCK */\n         switch (Op) {\n         case OpStart:\n            conn = Dpi_conn_new(Info);\n            Info->LocalKey = conn;\n\n            /* Hack: for receiving HTTP through the DPI framework */\n            if (strcmp(Data2, \"http\") == 0) {\n               conn->Send2EOF = 1;\n            }\n\n            a_Chain_link_new(Info, a_Dpi_ccc, BCK, a_IO_ccc, 2, 2);\n            a_Chain_bcb(OpStart, Info, NULL, NULL); /* IORead */\n            break;\n         case OpSend:\n            if (Data2 && !strcmp(Data2, \"FD\")) {\n               a_Chain_bcb(OpSend, Info, Data1, Data2);\n            }\n            break;\n         case OpAbort:\n            a_Chain_bcb(OpAbort, Info, NULL, NULL);\n            Dpi_conn_free(Info->LocalKey);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC\\n\");\n            break;\n         }\n      }\n   }\n}\n\n/*! Let dpid know dillo is no longer running.\n * Note: currently disabled. It may serve to let the cookies dpi know\n * when to expire session cookies.\n */\nvoid a_Dpi_dillo_exit()\n{\n\n}\n\n/*\n * Send a command to a dpi server, and block until the answer is got.\n * Return value: the dpip tag answer as an string, NULL on error.\n */\nchar *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd)\n{\n   int cst, sock_fd;\n   char *ret = NULL;\n\n   /* test the dpid, and wait a bit for it to start if necessary */\n   if ((cst = Dpi_blocking_start_dpid()) != 0) {\n      return ret;\n   }\n\n   if ((sock_fd = Dpi_connect_socket(server_name)) == -1) {\n      MSG_ERR(\"[a_Dpi_send_blocking_cmd] Can't connect to server.\\n\");\n   } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) {\n      MSG_ERR(\"[a_Dpi_send_blocking_cmd] Can't send message.\\n\");\n   } else if ((ret = Dpi_blocking_read(sock_fd)) == NULL) {\n      MSG_ERR(\"[a_Dpi_send_blocking_cmd] Can't read message.\\n\");\n   }\n   dClose(sock_fd);\n\n   return ret;\n}\n\n"
  },
  {
    "path": "src/IO/http.c",
    "content": "/*\n * File: http.c\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * HTTP connect functions\n */\n\n\n#include <config.h>\n\n#include <ctype.h>              /* isdigit */\n#include <unistd.h>\n#include <errno.h>              /* for errno */\n#include <stdlib.h>\n#include <fcntl.h>\n#include <assert.h>\n#include <sys/socket.h>         /* for lots of socket stuff */\n#include <netinet/in.h>         /* for ntohl and stuff */\n#include <arpa/inet.h>          /* for inet_ntop */\n\n#include \"IO.h\"\n#include \"iowatch.hh\"\n#include \"tls.h\"\n#include \"Url.h\"\n#include \"../msg.h\"\n#include \"../klist.h\"\n#include \"../dns.h\"\n#include \"../web.hh\"\n#include \"../cookies.h\"\n#include \"../auth.h\"\n#include \"../prefs.h\"\n#include \"../misc.h\"\n\n#include \"../uicmd.hh\"\n\n/* Used to send a message to the bw's status bar */\n#define MSG_BW(web, root, ...)                                        \\\nD_STMT_START {                                                        \\\n   if (a_Web_valid((web)) && (!(root) || (web)->flags & WEB_RootUrl)) \\\n      a_UIcmd_set_msg((web)->bw, __VA_ARGS__);                        \\\n} D_STMT_END\n\n#define _MSG_BW(web, root, ...)\n\nstatic const int HTTP_SOCKET_USE_PROXY   = 0x1;\nstatic const int HTTP_SOCKET_QUEUED      = 0x2;\nstatic const int HTTP_SOCKET_TO_BE_FREED = 0x4;\nstatic const int HTTP_SOCKET_TLS         = 0x8;\nstatic const int HTTP_SOCKET_IOWATCH_ACTIVE = 0x10;\n\n/* 'web' is just a reference (no need to deallocate it here). */\ntypedef struct {\n   int SockFD;\n   uint_t flags;\n   DilloWeb *web;          /* reference to client's web structure */\n   DilloUrl *url;\n   Dlist *addr_list;       /* Holds the DNS answer */\n   int addr_list_idx;\n   ChainLink *Info;        /* Used for CCC asynchronous operations */\n   char *connected_to;     /* Used for per-server connection limit */\n   uint_t connect_port;\n   Dstr *https_proxy_reply;\n} SocketData_t;\n\n/* Data structures and functions to queue sockets that need to be\n * delayed due to the per host connection limit.\n */\ntypedef struct {\n  char *host;\n  uint_t port;\n  bool_t https;\n\n  int active_conns;\n  int running_the_queue;\n  Dlist *queue;\n} Server_t;\n\ntypedef struct {\n   int fd;\n   int skey;\n} FdMapEntry_t;\n\nstatic void Http_socket_enqueue(Server_t *srv, SocketData_t* sock);\nstatic Server_t *Http_server_get(const char *host, uint_t port, bool_t https);\nstatic void Http_server_remove(Server_t *srv);\nstatic void Http_connect_socket(ChainLink *Info);\nstatic char *Http_get_connect_str(const DilloUrl *url);\nstatic void Http_send_query(SocketData_t *S);\nstatic void Http_socket_free(int SKey);\n\n/*\n * Local data\n */\nstatic Klist_t *ValidSocks = NULL; /* Active sockets list. It holds pointers to\n                                    * SocketData_t structures. */\nstatic DilloUrl *HTTP_Proxy = NULL;\nstatic char *HTTP_Proxy_Auth_base64 = NULL;\nstatic char *HTTP_Language_hdr = NULL;\nstatic Dlist *servers;\n\n/* TODO: If fd_map will stick around in its present form (FDs and SocketData_t)\n * then consider whether having both this and ValidSocks is necessary.\n */\nstatic Dlist *fd_map;\n\n/*\n * Initialize proxy vars and Accept-Language header\n */\nint a_Http_init(void)\n{\n   char *env_proxy = getenv(\"http_proxy\");\n\n   HTTP_Language_hdr = prefs.http_language ?\n      dStrconcat(\"Accept-Language: \", prefs.http_language, \"\\r\\n\", NULL) :\n      dStrdup(\"\");\n\n   if (env_proxy && strlen(env_proxy))\n      HTTP_Proxy = a_Url_new(env_proxy, NULL);\n   if (!HTTP_Proxy && prefs.http_proxy)\n      HTTP_Proxy = a_Url_dup(prefs.http_proxy);\n\n/*  This allows for storing the proxy password in \"user:passwd\" format\n * in dillorc, but as this constitutes a security problem, it was disabled.\n *\n   if (HTTP_Proxy && prefs.http_proxyuser && strchr(prefs.http_proxyuser, ':'))\n      HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(prefs.http_proxyuser);\n */\n\n   servers = dList_new(5);\n   fd_map = dList_new(20);\n\n   return 0;\n}\n\n/*\n * Tell whether the proxy auth is already set (user:password)\n * Return: 1 Yes, 0 No\n */\nint a_Http_proxy_auth(void)\n{\n   return (HTTP_Proxy_Auth_base64 ? 1 : 0);\n}\n\n/*\n * Activate entered proxy password for HTTP.\n */\nvoid a_Http_set_proxy_passwd(const char *str)\n{\n   char *http_proxyauth = dStrconcat(prefs.http_proxyuser, \":\", str, NULL);\n   HTTP_Proxy_Auth_base64 = a_Misc_encode_base64(http_proxyauth);\n   dFree(http_proxyauth);\n}\n\n/*\n * Create and init a new SocketData_t struct, insert into ValidSocks,\n * and return a primary key for it.\n */\nstatic int Http_sock_new(void)\n{\n   SocketData_t *S = dNew0(SocketData_t, 1);\n   S->SockFD = -1;\n   return a_Klist_insert(&ValidSocks, S);\n}\n\n/*\n * Compare by FD.\n */\nstatic int Http_fd_map_cmp(const void *v1, const void *v2)\n{\n   int fd = VOIDP2INT(v2);\n   const FdMapEntry_t *e = v1;\n\n   return (fd != e->fd);\n}\n\nstatic void Http_fd_map_add_entry(SocketData_t *sd)\n{\n   FdMapEntry_t *e = dNew0(FdMapEntry_t, 1);\n   e->fd = sd->SockFD;\n   e->skey = VOIDP2INT(sd->Info->LocalKey);\n\n   if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Http_fd_map_cmp)) {\n      MSG_ERR(\"FD ENTRY ALREADY FOUND FOR %d\\n\", e->fd);\n      assert(0);\n   }\n\n   dList_append(fd_map, e);\n}\n\n/*\n * Remove and free entry from fd_map.\n */\nstatic void Http_fd_map_remove_entry(int fd)\n{\n   void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Http_fd_map_cmp);\n\n   if (data) {\n      dList_remove_fast(fd_map, data);\n      dFree(data);\n   } else {\n      MSG(\"FD ENTRY NOT FOUND FOR %d\\n\", fd);\n   }\n}\n\nvoid a_Http_connect_done(int fd, bool_t success)\n{\n   SocketData_t *sd;\n   FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),\n                                            Http_fd_map_cmp);\n\n   if (fme && (sd = a_Klist_get_data(ValidSocks, fme->skey))) {\n      ChainLink *info = sd->Info;\n      bool_t valid_web = a_Web_valid(sd->web);\n\n      if (success && valid_web) {\n         a_Chain_bfcb(OpSend, info, &sd->SockFD, \"FD\");\n         Http_send_query(sd);\n      } else {\n         if (valid_web)\n            MSG_BW(sd->web, 1, \"Could not establish connection.\");\n         MSG(\"fd %d is done and failed\\n\", sd->SockFD);\n         dClose(fd);\n         Http_socket_free(VOIDP2INT(info->LocalKey)); /* free sd */\n         a_Chain_bfcb(OpAbort, info, NULL, \"Both\");\n         dFree(info);\n      }\n   } else {\n      MSG(\"**** but no luck with fme %p or sd\\n\", fme);\n   }\n}\n\nstatic void Http_socket_activate(Server_t *srv, SocketData_t *sd)\n{\n   dList_remove(srv->queue, sd);\n   sd->flags &= ~HTTP_SOCKET_QUEUED;\n   srv->active_conns++;\n   sd->connected_to = srv->host;\n}\n\nstatic void Http_connect_queued_sockets(Server_t *srv)\n{\n   SocketData_t *sd;\n   int i;\n\n   srv->running_the_queue++;\n\n   for (i = 0;\n        (i < dList_length(srv->queue) &&\n         srv->active_conns < prefs.http_max_conns);\n        i++) {\n      sd = dList_nth_data(srv->queue, i);\n\n      if (sd->flags & HTTP_SOCKET_TO_BE_FREED) {\n         dList_remove(srv->queue, sd);\n         dFree(sd);\n         i--;\n      } else {\n         int connect_ready = TLS_CONNECT_READY;\n\n         if (sd->flags & HTTP_SOCKET_TLS)\n            connect_ready = a_Tls_connect_ready(sd->url);\n\n         if (connect_ready == TLS_CONNECT_NEVER || !a_Web_valid(sd->web)) {\n            int SKey = VOIDP2INT(sd->Info->LocalKey);\n\n            Http_socket_free(SKey);\n         } else if (connect_ready == TLS_CONNECT_READY) {\n            i--;\n            Http_socket_activate(srv, sd);\n            Http_connect_socket(sd->Info);\n         }\n      }\n   }\n\n   _MSG(\"Queue http%s://%s:%u len %d\\n\", srv->https ? \"s\" : \"\", srv->host,\n        srv->port, dList_length(srv->queue));\n\n   if (--srv->running_the_queue == 0) {\n      if (srv->active_conns == 0)\n         Http_server_remove(srv);\n   }\n}\n\n/*\n * Free SocketData_t struct\n */\nstatic void Http_socket_free(int SKey)\n{\n   SocketData_t *S;\n\n   if ((S = a_Klist_get_data(ValidSocks, SKey))) {\n      a_Klist_remove(ValidSocks, SKey);\n\n      if (S->flags & HTTP_SOCKET_IOWATCH_ACTIVE) {\n         S->flags &= ~HTTP_SOCKET_IOWATCH_ACTIVE;\n         a_IOwatch_remove_fd(S->SockFD, -1);\n         dClose(S->SockFD);\n      }\n      dStr_free(S->https_proxy_reply, 1);\n\n      if (S->flags & HTTP_SOCKET_QUEUED) {\n         S->flags |= HTTP_SOCKET_TO_BE_FREED;\n         a_Url_free(S->url);\n      } else {\n         if (S->SockFD != -1)\n            Http_fd_map_remove_entry(S->SockFD);\n         a_Tls_reset_server_state(S->url);\n         if (S->connected_to) {\n            a_Tls_close_by_fd(S->SockFD);\n\n            Server_t *srv = Http_server_get(S->connected_to, S->connect_port,\n                                            (S->flags & HTTP_SOCKET_TLS));\n            srv->active_conns--;\n            Http_connect_queued_sockets(srv);\n         }\n         a_Url_free(S->url);\n         dFree(S);\n      }\n   }\n}\n\n/*\n * Make the HTTP header's Referer line according to preferences\n * (default is \"host\" i.e. \"scheme://hostname/\" )\n */\nstatic char *Http_get_referer(const DilloUrl *url)\n{\n   char *referer = NULL;\n\n   if (!strcmp(prefs.http_referer, \"host\")) {\n      referer = dStrconcat(\"Referer: \", URL_SCHEME(url), \"://\",\n                           URL_AUTHORITY(url), \"/\", \"\\r\\n\", NULL);\n   } else if (!strcmp(prefs.http_referer, \"path\")) {\n      referer = dStrconcat(\"Referer: \", URL_SCHEME(url), \"://\",\n                           URL_AUTHORITY(url),\n                           URL_PATH_(url) ? URL_PATH(url) : \"/\", \"\\r\\n\", NULL);\n   }\n   if (!referer)\n      referer = dStrdup(\"\");\n   _MSG(\"http, referer='%s'\\n\", referer);\n   return referer;\n}\n\n/*\n * Generate Content-Type header value for a POST query.\n */\nstatic Dstr *Http_make_content_type(const DilloUrl *url)\n{\n   Dstr *dstr;\n\n   if (URL_FLAGS(url) & URL_MultipartEnc) {\n      _MSG(\"submitting multipart/form-data!\\n\");\n      dstr = dStr_new(\"multipart/form-data; boundary=\\\"\");\n      if (URL_DATA(url)->len > 2) {\n         /* boundary lines have \"--\" prepended. Skip that. */\n         const char *start = URL_DATA(url)->str + 2;\n         char *eol = strchr(start, '\\r');\n         if (eol)\n            dStr_append_l(dstr, start, eol - start);\n      } else {\n         /* Zero parts; arbitrary boundary */\n         dStr_append_c(dstr, '0');\n      }\n      dStr_append_c(dstr,'\"');\n   } else {\n      dstr = dStr_new(\"application/x-www-form-urlencoded\");\n   }\n   return dstr;\n}\n\n/*\n * Make the http query string\n */\nstatic Dstr *Http_make_query_str(DilloWeb *web, bool_t use_proxy)\n{\n   char *ptr, *referer, *auth;\n   char *cookies = NULL;\n   const DilloUrl *url = web->url;\n   Dstr *query      = dStr_new(\"\"),\n        *request_uri = dStr_new(\"\"),\n        *proxy_auth = dStr_new(\"\");\n\n   /* BUG: dillo doesn't actually understand application/xml yet */\n   const char *accept_hdr_value =\n      web->flags & WEB_Image ? \"image/png,image/*;q=0.8,*/*;q=0.5\" :\n      web->flags & WEB_Stylesheet ? \"text/css,*/*;q=0.1\" :\n      \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\";\n\n   const char *connection_hdr_val =\n      (prefs.http_persistent_conns == TRUE) ? \"keep-alive\" : \"close\";\n\n   if (use_proxy) {\n      dStr_sprintfa(request_uri, \"%s%s\",\n                    URL_STR(url),\n                    (URL_PATH_(url) || URL_QUERY_(url)) ? \"\" : \"/\");\n      if ((ptr = strrchr(request_uri->str, '#')))\n         dStr_truncate(request_uri, ptr - request_uri->str);\n      if (HTTP_Proxy_Auth_base64)\n         dStr_sprintf(proxy_auth, \"Proxy-Authorization: Basic %s\\r\\n\",\n                      HTTP_Proxy_Auth_base64);\n   } else {\n      dStr_sprintfa(request_uri, \"%s%s%s%s\",\n                    URL_PATH(url),\n                    URL_QUERY_(url) ? \"?\" : \"\",\n                    URL_QUERY(url),\n                    (URL_PATH_(url) || URL_QUERY_(url)) ? \"\" : \"/\");\n   }\n\n   if(prefs.use_cookies)\n      cookies = a_Cookies_get_query(url, web->requester);\n   auth = a_Auth_get_auth_str(url, request_uri->str);\n   referer = Http_get_referer(url);\n   if (URL_FLAGS(url) & URL_Post) {\n      Dstr *content_type = Http_make_content_type(url);\n      dStr_sprintfa(\n         query,\n         \"POST %s HTTP/1.1\\r\\n\"\n         \"Host: %s\\r\\n\"\n         \"User-Agent: %s\\r\\n\"\n         \"Accept: %s\\r\\n\"\n         \"%s\" /* language */\n         \"Accept-Encoding: gzip, deflate\\r\\n\"\n         \"%s\" /* auth */\n         \"DNT: 1\\r\\n\"\n         \"%s\" /* proxy auth */\n         \"%s\" /* referer */\n         \"Connection: %s\\r\\n\"\n         \"Content-Type: %s\\r\\n\"\n         \"Content-Length: %ld\\r\\n\"\n         \"%s\" /* cookies */\n         \"\\r\\n\",\n         request_uri->str, URL_AUTHORITY(url), prefs.http_user_agent,\n         accept_hdr_value, HTTP_Language_hdr, auth ? auth : \"\",\n         proxy_auth->str, referer, connection_hdr_val, content_type->str,\n         (long)URL_DATA(url)->len, prefs.use_cookies ? cookies : \"\");\n      dStr_append_l(query, URL_DATA(url)->str, URL_DATA(url)->len);\n      dStr_free(content_type, TRUE);\n   } else {\n      dStr_sprintfa(\n         query,\n         \"GET %s HTTP/1.1\\r\\n\"\n         \"Host: %s\\r\\n\"\n         \"User-Agent: %s\\r\\n\"\n         \"Accept: %s\\r\\n\"\n         \"%s\" /* language */\n         \"Accept-Encoding: gzip, deflate\\r\\n\"\n         \"%s\" /* auth */\n         \"DNT: 1\\r\\n\"\n         \"%s\" /* proxy auth */\n         \"%s\" /* referer */\n         \"Connection: %s\\r\\n\"\n         \"%s\" /* cache control */\n         \"%s\" /* cookies */\n         \"\\r\\n\",\n         request_uri->str, URL_AUTHORITY(url), prefs.http_user_agent,\n         accept_hdr_value, HTTP_Language_hdr, auth ? auth : \"\",\n         proxy_auth->str, referer, connection_hdr_val,\n         (URL_FLAGS(url) & URL_E2EQuery) ?\n            \"Pragma: no-cache\\r\\nCache-Control: no-cache\\r\\n\" : \"\",\n         prefs.use_cookies ? cookies : \"\");\n   }\n   dFree(referer);\n   if(prefs.use_cookies)\n      dFree(cookies);\n   dFree(auth);\n\n   dStr_free(request_uri, TRUE);\n   dStr_free(proxy_auth, TRUE);\n   _MSG(\"Query: {%s}\\n\", dStr_printable(query, 8192));\n   return query;\n}\n\n/*\n * Create and submit the HTTP query to the IO engine\n */\nstatic void Http_send_query(SocketData_t *S)\n{\n   Dstr *query;\n   DataBuf *dbuf;\n\n   /* Create the query */\n   query = Http_make_query_str(S->web, S->flags & HTTP_SOCKET_USE_PROXY);\n   dbuf = a_Chain_dbuf_new(query->str, query->len, 0);\n\n   MSG_BW(S->web, 1, \"Sending query%s...\",\n                     S->flags & HTTP_SOCKET_USE_PROXY ? \" through proxy\" : \"\");\n\n   /* send query */\n   a_Chain_bcb(OpSend, S->Info, dbuf, NULL);\n   dFree(dbuf);\n   dStr_free(query, 1);\n}\n\n/*\n * Prepare an HTTPS connection.  If necessary, tunnel through a proxy first.\n */\nstatic void Http_connect_tls(ChainLink *info)\n{\n   int SKey = VOIDP2INT(info->LocalKey);\n   SocketData_t *S = a_Klist_get_data(ValidSocks, SKey);\n\n   if (S->flags & HTTP_SOCKET_USE_PROXY) {\n      char *connect_str = Http_get_connect_str(S->url);\n      DataBuf *dbuf = a_Chain_dbuf_new(connect_str, strlen(connect_str), 0);\n\n      MSG_BW(S->web, 1, \"Tunnel secure connection through proxy...\");\n      a_Chain_bfcb(OpSend, info, &S->SockFD, \"FD\");\n      S->https_proxy_reply = dStr_new(NULL);\n      a_Chain_bcb(OpSend, info, dbuf, NULL);\n\n      dFree(dbuf);\n      dFree(connect_str);\n   } else {\n      MSG_BW(S->web, 1, \"Secure connection negotiation...\");\n      a_Tls_connect(S->SockFD, S->url);\n   }\n}\n\n/*\n * connect() couldn't complete before, but now it's ready, so let's try again.\n */\nstatic void Http_connect_socket_cb(int fd, void *data)\n{\n   int SKey = VOIDP2INT(data);\n   SocketData_t *S = a_Klist_get_data(ValidSocks, SKey);\n\n   if (S) {\n      int ret, connect_ret;\n      uint_t connect_ret_size = sizeof(connect_ret);\n\n      a_IOwatch_remove_fd(fd, -1); \n      S->flags &= ~HTTP_SOCKET_IOWATCH_ACTIVE;\n\n      ret = getsockopt(S->SockFD, SOL_SOCKET, SO_ERROR, &connect_ret,\n                       &connect_ret_size);\n\n      if (ret < 0 || connect_ret != 0) {\n         if (ret < 0) {\n            MSG(\"Http_connect_socket_cb getsockopt ERROR: %s.\\n\",\n                dStrerror(errno));\n         } else {\n            MSG(\"Http_connect_socket_cb connect ERROR: %s.\\n\",\n                dStrerror(connect_ret));\n         }\n         MSG(\"Http_connect_socket() will try another IP address.\\n\");\n         S->addr_list_idx++;\n         Http_connect_socket(S->Info);\n      } else if (S->flags & HTTP_SOCKET_TLS) {\n         Http_connect_tls(S->Info);\n      } else {\n         a_Http_connect_done(S->SockFD, TRUE);\n      }\n   }\n}\n\n/*\n * This function is called after the DNS succeeds in solving a hostname.\n * Task: Finish socket setup and start connecting the socket.\n */\nstatic void Http_connect_socket(ChainLink *Info)\n{\n   DilloHost *dh;\n   SocketData_t *S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey));\n\n   for (; (dh = dList_nth_data(S->addr_list, S->addr_list_idx));\n        S->addr_list_idx++) {\n#ifdef ENABLE_IPV6\n      struct sockaddr_in6 name;\n#else\n      struct sockaddr_in name;\n#endif\n      socklen_t socket_len = 0;\n\n      if (S->addr_list_idx > 0 && S->SockFD >= 0) {\n         /* clean up the previous one that failed */\n         Http_fd_map_remove_entry(S->SockFD);\n         dClose(S->SockFD);\n      }\n      if ((S->SockFD = socket(dh->af, SOCK_STREAM, IPPROTO_TCP)) < 0) {\n         MSG(\"Http_connect_socket socket() ERROR: %s\\n\", dStrerror(errno));\n         continue;\n      }\n      Http_fd_map_add_entry(S);\n\n      /* set NONBLOCKING and close on exec. */\n      fcntl(S->SockFD, F_SETFL, O_NONBLOCK | fcntl(S->SockFD, F_GETFL));\n      fcntl(S->SockFD, F_SETFD, FD_CLOEXEC | fcntl(S->SockFD, F_GETFD));\n\n      /* Some OSes require this...  */\n      memset(&name, 0, sizeof(name));\n      /* Set remaining parms. */\n      switch (dh->af) {\n      case AF_INET:\n      {\n         struct sockaddr_in *sin = (struct sockaddr_in *)&name;\n         socket_len = sizeof(struct sockaddr_in);\n         sin->sin_family = dh->af;\n         sin->sin_port = htons(S->connect_port);\n         memcpy(&sin->sin_addr, dh->data, (size_t)dh->alen);\n         if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl))\n            MSG(\"Connecting to %s:%u\\n\", inet_ntoa(sin->sin_addr),\n                S->connect_port);\n         break;\n      }\n#ifdef ENABLE_IPV6\n      case AF_INET6:\n      {\n         char buf[128];\n         struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&name;\n         socket_len = sizeof(struct sockaddr_in6);\n         sin6->sin6_family = dh->af;\n         sin6->sin6_port = htons(S->connect_port);\n         memcpy(&sin6->sin6_addr, dh->data, dh->alen);\n         inet_ntop(dh->af, dh->data, buf, sizeof(buf));\n         if (a_Web_valid(S->web) && (S->web->flags & WEB_RootUrl))\n            MSG(\"Connecting to [%s]:%u\\n\", buf, S->connect_port);\n         break;\n      }\n#endif\n      } /* switch */\n      MSG_BW(S->web, 1, \"Contacting host...\");\n\n      if (connect(S->SockFD, (struct sockaddr *)&name, socket_len) == 0) {\n         /* probably never succeeds immediately on any system */\n         if (S->flags & HTTP_SOCKET_TLS) {\n            Http_connect_tls(Info);\n            break;\n         } else {\n            a_Http_connect_done(S->SockFD, TRUE);\n            break;\n         }\n      } else {\n         if (errno == EINPROGRESS) {\n            a_IOwatch_add_fd(S->SockFD, DIO_WRITE, Http_connect_socket_cb,\n                             Info->LocalKey);\n            S->flags |= HTTP_SOCKET_IOWATCH_ACTIVE;\n            break;\n         } else {\n            MSG(\"Http_connect_socket connect ERROR: %s\\n\", dStrerror(errno));\n            MSG(\"We will try another IP address.\\n\");\n         }\n      }\n   } /* for */\n\n   if (S->addr_list_idx >= dList_length(S->addr_list) ) {\n      MSG(\"Http_connect_socket ran out of IP addrs to try.\\n\");\n      a_Http_connect_done(S->SockFD, FALSE);\n   }\n}\n\n/*\n * Test proxy settings and check the no_proxy domains list\n * Return value: whether to use proxy or not.\n */\nstatic int Http_must_use_proxy(const char *hostname)\n{\n   char *np, *p, *tok;\n   int ret = 0;\n\n   if (HTTP_Proxy) {\n      ret = 1;\n      if (prefs.no_proxy) {\n         size_t host_len = strlen(hostname);\n\n         np = dStrdup(prefs.no_proxy);\n         for (p = np; (tok = dStrsep(&p, \" \"));  ) {\n            int start = host_len - strlen(tok);\n\n            if (start >= 0 && dStrAsciiCasecmp(hostname + start, tok) == 0) {\n               /* no_proxy token is suffix of host string */\n               ret = 0;\n               break;\n            }\n         }\n         dFree(np);\n      }\n   }\n   _MSG(\"Http_must_use_proxy: %s\\n  %s\\n\", hostname, ret ? \"YES\":\"NO\");\n   return ret;\n}\n\n/*\n * Return a new string for the request used to tunnel HTTPS through a proxy.\n */\nstatic char *Http_get_connect_str(const DilloUrl *url)\n{\n   Dstr *dstr;\n   const char *auth1;\n   int auth_len;\n   char *auth2, *proxy_auth, *retstr;\n\n   dReturn_val_if_fail(Http_must_use_proxy(URL_HOST(url)), NULL);\n\n   dstr = dStr_new(\"\");\n   auth1 = URL_AUTHORITY(url);\n   auth_len = strlen(auth1);\n   if (auth_len > 0 && !isdigit(auth1[auth_len - 1]))\n      /* if no port number, add HTTPS port */\n      auth2 = dStrconcat(auth1, \":443\", NULL);\n   else\n      auth2 = dStrdup(auth1);\n   proxy_auth = HTTP_Proxy_Auth_base64 ?\n                   dStrconcat (\"Proxy-Authorization: Basic \",\n                               HTTP_Proxy_Auth_base64, \"\\r\\n\", NULL) :\n                   dStrdup(\"\");\n   dStr_sprintfa(\n      dstr,\n      \"CONNECT %s HTTP/1.1\\r\\n\"\n      \"Host: %s\\r\\n\"\n      \"%s\"\n      \"\\r\\n\",\n      auth2,\n      auth2,\n      proxy_auth);\n\n   dFree(auth2);\n   dFree(proxy_auth);\n   retstr = dstr->str;\n   dStr_free(dstr, 0);\n   return retstr;\n}\n\n/*\n * Callback function for the DNS resolver.\n * Continue connecting the socket, or abort upon error condition.\n * S->web is checked to assert the operation wasn't aborted while waiting.\n */\nstatic void Http_dns_cb(int Status, Dlist *addr_list, void *data)\n{\n   int SKey = VOIDP2INT(data);\n   bool_t clean_up = TRUE;\n   SocketData_t *S;\n   Server_t *srv;\n\n   S = a_Klist_get_data(ValidSocks, SKey);\n   if (S) {\n      const char *host = URL_HOST((S->flags & HTTP_SOCKET_USE_PROXY) ?\n                                  HTTP_Proxy : S->url);\n      if (a_Web_valid(S->web)) {\n         if (Status == 0 && addr_list) {\n\n            /* Successful DNS answer; save the IP */\n            S->addr_list = addr_list;\n            S->addr_list_idx = 0;\n            clean_up = FALSE;\n            srv = Http_server_get(host, S->connect_port,\n                                 (S->flags & HTTP_SOCKET_TLS));\n            Http_socket_enqueue(srv, S);\n            Http_connect_queued_sockets(srv);\n         } else {\n            /* DNS wasn't able to resolve the hostname */\n            MSG_BW(S->web, 0, \"ERROR: DNS can't resolve %s\", host);\n         }\n      }\n      if (clean_up) {\n         ChainLink *info = S->Info;\n\n         Http_socket_free(SKey);\n         a_Chain_bfcb(OpAbort, info, NULL, \"Both\");\n         dFree(info);\n      }\n   }\n}\n\n/*\n * Asynchronously create a new http connection for 'Url'\n * We'll set some socket parameters; the rest will be set later\n * when the IP is known.\n * ( Data1 = Web structure )\n * Return value: 0 on success, -1 otherwise\n */\nstatic int Http_get(ChainLink *Info, void *Data1)\n{\n   SocketData_t *S;\n   char *hostname;\n   const DilloUrl *url;\n\n   S = a_Klist_get_data(ValidSocks, VOIDP2INT(Info->LocalKey));\n   /* Reference Web data */\n   S->web = Data1;\n   /* Reference Info data */\n   S->Info = Info;\n\n   /* Proxy support */\n   if (Http_must_use_proxy(URL_HOST(S->web->url))) {\n      url = HTTP_Proxy;\n      S->flags |= HTTP_SOCKET_USE_PROXY;\n   } else {\n      url = S->web->url;\n   }\n   hostname = dStrdup(URL_HOST(url));\n   S->connect_port = URL_PORT(url);\n   S->url = a_Url_dup(S->web->url);\n   if (!dStrAsciiCasecmp(URL_SCHEME(S->url), \"https\"))\n      S->flags |= HTTP_SOCKET_TLS;\n\n   /* Let the user know what we'll do */\n   MSG_BW(S->web, 1, \"DNS resolving %s\", hostname);\n\n   /* Let the DNS engine resolve the hostname, and when done,\n    * we'll try to connect the socket from the callback function */\n   a_Dns_resolve(hostname, Http_dns_cb, Info->LocalKey);\n\n   dFree(hostname);\n   return 0;\n}\n\n/*\n * Can the old socket's fd be reused for the new socket?\n *\n * NOTE: old and new must come from the same Server_t.\n * This is not built to accept arbitrary sockets.\n */\nstatic bool_t Http_socket_reuse_compatible(SocketData_t *old,\n                                           SocketData_t *new)\n{\n   /*\n    * If we are using TLS through a proxy, we need to ensure that old and new\n    * are going through to the same host:port.\n    */\n   if (a_Web_valid(new->web) &&\n       ((old->flags & HTTP_SOCKET_TLS) == 0 ||\n        (old->flags & HTTP_SOCKET_USE_PROXY) == 0 ||\n        ((URL_PORT(old->url) == URL_PORT(new->url)) &&\n         !dStrAsciiCasecmp(URL_HOST(old->url), URL_HOST(new->url)))))\n      return TRUE;\n   return FALSE;\n}\n\n/*\n * If any entry in the socket data queue can reuse our connection, set it up\n * and send off a new query.\n */\nstatic void Http_socket_reuse(int SKey)\n{\n   SocketData_t *new_sd, *old_sd = a_Klist_get_data(ValidSocks, SKey);\n\n   if (old_sd) {\n      Server_t *srv = Http_server_get(old_sd->connected_to,\n                                      old_sd->connect_port,\n                                      (old_sd->flags & HTTP_SOCKET_TLS));\n      int i, n = dList_length(srv->queue);\n\n      for (i = 0; i < n; i++) {\n         new_sd = dList_nth_data(srv->queue, i);\n\n         if (!(new_sd->flags & HTTP_SOCKET_TO_BE_FREED) &&\n             Http_socket_reuse_compatible(old_sd, new_sd)) {\n            const bool_t success = TRUE;\n\n            new_sd->SockFD = old_sd->SockFD;\n\n            old_sd->connected_to = NULL;\n            srv->active_conns--;\n            Http_socket_free(SKey);\n\n            _MSG(\"Reusing fd %d for %s\\n\",new_sd->SockFD,URL_STR(new_sd->url));\n            Http_socket_activate(srv, new_sd);\n            Http_fd_map_add_entry(new_sd);\n            a_Http_connect_done(new_sd->SockFD, success);\n            return;\n         }\n      }\n      dClose(old_sd->SockFD);\n      Http_socket_free(SKey);\n   }\n}\n\n/*\n * CCC function for the HTTP module\n */\nvoid a_Http_ccc(int Op, int Branch, int Dir, ChainLink *Info,\n                void *Data1, void *Data2)\n{\n   int SKey = VOIDP2INT(Info->LocalKey);\n   SocketData_t *sd;\n   DataBuf *dbuf;\n\n   dReturn_if_fail( a_Chain_check(\"a_Http_ccc\", Op, Branch, Dir, Info) );\n\n   if (Branch == 1) {\n      if (Dir == BCK) {\n         /* HTTP query branch */\n         switch (Op) {\n         case OpStart:\n            /* ( Data1 = Web ) */\n            SKey = Http_sock_new();\n            Info->LocalKey = INT2VOIDP(SKey);\n            /* link IO */\n            a_Chain_link_new(Info, a_Http_ccc, BCK, a_IO_ccc, 1, 1);\n            a_Chain_bcb(OpStart, Info, NULL, NULL);\n            /* async. connection */\n            Http_get(Info, Data1);\n            break;\n         case OpEnd:\n            /* finished the HTTP query branch */\n            a_Chain_bcb(OpEnd, Info, NULL, NULL);\n            dFree(Info);\n            break;\n         case OpAbort:\n            _MSG(\"ABORT 1B\\n\");\n            Http_socket_free(SKey);\n            a_Chain_bcb(OpAbort, Info, NULL, NULL);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC 1B Op %d\\n\", Op);\n            break;\n         }\n      } else {  /* 1 FWD */\n         /* HTTP send-query status branch */\n         switch (Op) {\n         case OpAbort:\n            _MSG(\"ABORT 1F\\n\");\n            if ((sd = a_Klist_get_data(ValidSocks, SKey)))\n               MSG_BW(sd->web, 1, \"Can't get %s\", URL_STR(sd->url));\n            Http_socket_free(SKey);\n            a_Chain_fcb(OpAbort, Info, NULL, \"Both\");\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC 1F Op %d\\n\", Op);\n            break;\n         }\n      }\n   } else if (Branch == 2) {\n      if (Dir == FWD) {\n         sd = a_Klist_get_data(ValidSocks, SKey);\n         assert(sd);\n         /* Receiving from server */\n         switch (Op) {\n         case OpSend:\n            if (sd->https_proxy_reply) {\n               dbuf = Data1;\n               dStr_append(sd->https_proxy_reply, dbuf->Buf);\n               if (strstr(sd->https_proxy_reply->str, \"\\r\\n\\r\\n\")) {\n                  if (sd->https_proxy_reply->len >= 12 &&\n                      sd->https_proxy_reply->str[9] == '2') {\n                     /* e.g. \"HTTP/1.1 200 Connection established[...]\" */\n                     MSG(\"CONNECT through proxy succeeded. Reply:\\n%s\\n\",\n                         sd->https_proxy_reply->str);\n                     dStr_free(sd->https_proxy_reply, 1);\n                     sd->https_proxy_reply = NULL;\n                     MSG_BW(sd->web, 1, \"Secure connection negotiation...\");\n                     a_Tls_connect(sd->SockFD, sd->url);\n                  } else {\n                     MSG_BW(sd->web, 1, \"Can't connect through proxy to %s\",\n                            URL_HOST(sd->url));\n                     MSG(\"CONNECT through proxy failed. Server sent:\\n%s\\n\",\n                         sd->https_proxy_reply->str);\n                     Http_socket_free(SKey);\n                     a_Chain_bfcb(OpAbort, Info, NULL, \"Both\");\n                     dFree(Info);\n                  }\n               }\n            } else {\n               /* Data1 = dbuf */\n               a_Chain_fcb(OpSend, Info, Data1, \"send_page_2eof\");\n            }\n            break;\n         case OpEnd:\n            if (sd->https_proxy_reply) {\n               MSG(\"CONNECT through proxy failed. \"\n                   \"Full reply not received:\\n%s\\n\",\n                   sd->https_proxy_reply->len ? sd->https_proxy_reply->str :\n                   \"(nothing)\");\n               Http_socket_free(SKey);\n               a_Chain_bfcb(OpAbort, Info, NULL, \"Both\");\n            } else {\n               Http_socket_free(SKey);\n               a_Chain_fcb(OpEnd, Info, NULL, NULL);\n            }\n            dFree(Info);\n            break;\n         case OpAbort:\n            if (sd->https_proxy_reply) {\n               MSG(\"CONNECT through proxy failed. \"\n                   \"Full reply not received:\\n%s\\n\",\n                   sd->https_proxy_reply->len ? sd->https_proxy_reply->str :\n                   \"(nothing)\");\n            }\n            Http_socket_free(SKey);\n            a_Chain_fcb(OpAbort, Info, NULL, \"Both\");\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC 2F Op %d\\n\", Op);\n            break;\n         }\n      } else {  /* 2 BCK */\n         switch (Op) {\n         case OpStart:\n            a_Chain_link_new(Info, a_Http_ccc, BCK, a_IO_ccc, 2, 2);\n            a_Chain_bcb(OpStart, Info, NULL, NULL); /* IORead */\n            break;\n         case OpSend:\n            if (Data2) {\n               if (!strcmp(Data2, \"FD\")) {\n                  int fd = *(int*)Data1;\n                  FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),\n                                                        Http_fd_map_cmp);\n                  Info->LocalKey = INT2VOIDP(fme->skey);\n                  a_Chain_bcb(OpSend, Info, Data1, Data2);\n               } else if (!strcmp(Data2, \"reply_complete\")) {\n                  a_Chain_bfcb(OpEnd, Info, NULL, NULL);\n                  Http_socket_reuse(SKey);\n                  dFree(Info);\n               }\n            }\n            break;\n         case OpAbort:\n            Http_socket_free(SKey);\n            a_Chain_bcb(OpAbort, Info, NULL, NULL);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC 2B Op %d\\n\", Op);\n            break;\n         }\n      }\n   }\n}\n\n/*\n * Add socket data to the queue. Pages/stylesheets/etc. have higher priority\n * than images.\n */\nstatic void Http_socket_enqueue(Server_t *srv, SocketData_t* sock)\n{\n   sock->flags |= HTTP_SOCKET_QUEUED;\n\n   if ((sock->web->flags & WEB_Image) == 0) {\n      int i, n = dList_length(srv->queue);\n\n      for (i = 0; i < n; i++) {\n         SocketData_t *curr = dList_nth_data(srv->queue, i);\n\n         if (a_Web_valid(curr->web) && (curr->web->flags & WEB_Image)) {\n            dList_insert_pos(srv->queue, sock, i);\n            return;\n         }\n      }\n   }\n   dList_append(srv->queue, sock);\n}\n\nstatic Server_t *Http_server_get(const char *host, uint_t port, bool_t https)\n{\n   int i;\n   Server_t *srv;\n\n   for (i = 0; i < dList_length(servers); i++) {\n      srv = (Server_t*) dList_nth_data(servers, i);\n\n      if (port == srv->port && https == srv->https &&\n          !dStrAsciiCasecmp(host, srv->host))\n         return srv;\n   }\n\n   srv = dNew0(Server_t, 1);\n   srv->queue = dList_new(10);\n   srv->running_the_queue = 0;\n   srv->host = dStrdup(host);\n   srv->port = port;\n   srv->https = https;\n   dList_append(servers, srv);\n\n   return srv;\n}\n\nstatic void Http_server_remove(Server_t *srv)\n{\n   SocketData_t *sd;\n\n   while ((sd = dList_nth_data(srv->queue, 0))) {\n      dList_remove_fast(srv->queue, sd);\n      dFree(sd);\n   }\n   dList_free(srv->queue);\n   dList_remove_fast(servers, srv);\n   dFree(srv->host);\n   dFree(srv);\n}\n\nstatic void Http_servers_remove_all()\n{\n   Server_t *srv;\n   SocketData_t *sd;\n\n   while (dList_length(servers) > 0) {\n      srv = (Server_t*) dList_nth_data(servers, 0);\n      while ((sd = dList_nth_data(srv->queue, 0))) {\n         dList_remove(srv->queue, sd);\n         dFree(sd);\n      }\n      Http_server_remove(srv);\n   }\n   dList_free(servers);\n}\n\nstatic void Http_fd_map_remove_all()\n{\n   FdMapEntry_t *fme;\n   int i, n = dList_length(fd_map);\n\n   for (i = 0; i < n; i++) {\n      fme = (FdMapEntry_t *) dList_nth_data(fd_map, i);\n      dFree(fme);\n   }\n   dList_free(fd_map);\n}\n\n/*\n * Deallocate memory used by http module\n * (Call this one at exit time)\n */\nvoid a_Http_freeall(void)\n{\n   Http_servers_remove_all();\n   Http_fd_map_remove_all();\n   a_Klist_free(&ValidSocks);\n   a_Url_free(HTTP_Proxy);\n   dFree(HTTP_Proxy_Auth_base64);\n   dFree(HTTP_Language_hdr);\n}\n"
  },
  {
    "path": "src/IO/iowatch.cc",
    "content": "/*\n * File: iowatch.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n// Simple ADT for watching file descriptor activity\n\n#include <FL/Fl.H>\n#include \"iowatch.hh\"\n\n//\n// Hook a Callback for a certain activities in a FD\n//\nvoid a_IOwatch_add_fd(int fd, int when, Fl_FD_Handler Callback,\n                      void *usr_data = 0)\n{\n   if (fd >= 0)\n      Fl::add_fd(fd, when, Callback, usr_data);\n}\n\n//\n// Remove a Callback for a given FD (or just remove some events)\n//\nvoid a_IOwatch_remove_fd(int fd, int when)\n{\n   if (fd >= 0)\n      Fl::remove_fd(fd, when);\n}\n\n"
  },
  {
    "path": "src/IO/iowatch.hh",
    "content": "#ifndef __IO_WATCH_H__\n#define __IO_WATCH_H__\n\n/*\n * BUG: enum {READ = 1, WRITE = 4, EXCEPT = 8} borrowed from FL/Enumerations.H\n */\n#define DIO_READ    1\n#define DIO_WRITE   4\n#define DIO_EXCEPT  8\n\ntypedef void (*CbFunction_t)(int fd, void *data);\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nvoid a_IOwatch_add_fd(int fd,int when,CbFunction_t Callback,void *usr_data);\nvoid a_IOwatch_remove_fd(int fd,int when);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __IO_WATCH_H__ */\n\n"
  },
  {
    "path": "src/IO/mime.c",
    "content": "/*\n * File: mime.c\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include \"mime.h\"\n#include \"../list.h\"\n\n\ntypedef struct {\n   const char *Name;   /* MIME type name */\n   Viewer_t Data;      /* Pointer to a function */\n} MimeItem_t;\n\n\n/*\n *  Local data\n */\nstatic int MimeMinItemsSize = 0, MimeMinItemsMax = 8;\nstatic MimeItem_t *MimeMinItems = NULL;\n\nstatic int MimeMajItemsSize = 0, MimeMajItemsMax = 8;\nstatic MimeItem_t *MimeMajItems = NULL;\n\n\n/*\n * Add a specific MIME type (as \"image/png\") to our viewer list\n * 'Key' is the content-type string that identifies the MIME type\n * 'Method' is the function that handles it\n */\nstatic int Mime_add_minor_type(const char *Key, Viewer_t Method)\n{\n   a_List_add(MimeMinItems, MimeMinItemsSize, MimeMinItemsMax);\n   MimeMinItems[MimeMinItemsSize].Name = Key;\n   MimeMinItems[MimeMinItemsSize].Data = Method;\n   MimeMinItemsSize++;\n   return 0;\n}\n\n/*\n * Add a major MIME type (as \"text\") to our viewer list\n * 'Key' is the content-type string that identifies the MIME type\n * 'Method' is the function that handles it\n */\nstatic int Mime_add_major_type(const char *Key, Viewer_t Method)\n{\n   a_List_add(MimeMajItems, MimeMajItemsSize, MimeMajItemsMax);\n   MimeMajItems[MimeMajItemsSize].Name = Key;\n   MimeMajItems[MimeMajItemsSize].Data = Method;\n   MimeMajItemsSize++;\n   return 0;\n}\n\n/*\n * Search the list of specific MIME viewers, for a Method that matches 'Key'\n * 'Key' is the content-type string that identifies the MIME type\n */\nstatic Viewer_t Mime_minor_type_fetch(const char *Key, uint_t Size)\n{\n   int i;\n\n   if (Size) {\n      for ( i = 0; i < MimeMinItemsSize; ++i )\n         if (dStrnAsciiCasecmp(Key, MimeMinItems[i].Name, Size) == 0)\n            return MimeMinItems[i].Data;\n   }\n   return NULL;\n}\n\n/*\n * Search the list of major MIME viewers, for a Method that matches 'Key'\n * 'Key' is the content-type string that identifies the MIME type\n */\nstatic Viewer_t Mime_major_type_fetch(const char *Key, uint_t Size)\n{\n   int i;\n\n   if (Size) {\n      for ( i = 0; i < MimeMajItemsSize; ++i )\n         if (dStrnAsciiCasecmp(Key, MimeMajItems[i].Name, Size) == 0)\n            return MimeMajItems[i].Data;\n   }\n   return NULL;\n}\n\n\n/*\n * Initializes Mime module and, sets the supported Mime types.\n */\nvoid a_Mime_init()\n{\n#ifdef ENABLE_GIF\n   Mime_add_minor_type(\"image/gif\", a_Dicache_gif_image);\n#endif\n#ifdef ENABLE_JPEG\n   Mime_add_minor_type(\"image/jpeg\", a_Dicache_jpeg_image);\n   Mime_add_minor_type(\"image/pjpeg\", a_Dicache_jpeg_image);\n   Mime_add_minor_type(\"image/jpg\", a_Dicache_jpeg_image);\n#endif\n#ifdef ENABLE_PNG\n   Mime_add_minor_type(\"image/png\", a_Dicache_png_image);\n   Mime_add_minor_type(\"image/x-png\", a_Dicache_png_image);    /* deprecated */\n#endif\n   Mime_add_minor_type(\"text/html\", a_Html_text);\n   Mime_add_minor_type(\"application/xhtml+xml\", a_Html_text);\n\n   Mime_add_minor_type(\"text/gemini\", a_Html_text);\n   Mime_add_minor_type(\"text/gopher\", a_Html_text);\n   Mime_add_minor_type(\"text/markdown\", a_Html_text);\n\n   Mime_add_minor_type(\"application/rss+xml\", a_Html_text);\n   Mime_add_minor_type(\"text/xml\", a_Html_text);\n\n   /* Add a major type to handle all the text stuff */\n   Mime_add_major_type(\"text\", a_Plain_text);\n}\n\n\n/*\n * Get the handler for the MIME type.\n *\n * Return Value:\n *   On success: viewer\n *   On failure: NULL\n */\nViewer_t a_Mime_get_viewer(const char *content_type)\n{\n   Viewer_t viewer;\n   uint_t MinSize, MajSize, i;\n   const char *str = content_type;\n\n   MajSize = 0;\n   for (i = 0; str[i] && str[i] != ' ' && str[i] != ';'; ++i) {\n      if (str[i] == '/' && !MajSize)\n         MajSize = i;\n   }\n   MinSize = i;\n\n   viewer = Mime_minor_type_fetch(content_type, MinSize);\n   if (!viewer)\n      viewer = Mime_major_type_fetch(content_type, MajSize);\n\n   return viewer;\n}\n"
  },
  {
    "path": "src/IO/mime.h",
    "content": "/*\n * File: mime.h\n *\n * Copyright (C) 2005 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef __MIME_H__\n#define __MIME_H__\n\n#include <config.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\n#include \"../cache.h\"\n\ntypedef void* (*Viewer_t) (const char*, void*, CA_Callback_t*, void**);\n\n/*\n * Function prototypes defined elsewhere\n */\nvoid *a_Html_text (const char *Type,void *web, CA_Callback_t *Call,\n                       void **Data);\nvoid *a_Plain_text(const char *Type,void *web, CA_Callback_t *Call,\n                       void **Data);\nvoid *a_Dicache_png_image (const char *Type,void *web, CA_Callback_t *Call,\n                           void **Data);\nvoid *a_Dicache_gif_image(const char *Type, void *Ptr, CA_Callback_t *Call,\n                          void **Data);\nvoid *a_Dicache_jpeg_image(const char *Type, void *Ptr, CA_Callback_t *Call,\n                           void **Data);\n\n/*\n * Functions defined inside Mime module\n */\nvoid a_Mime_init(void);\nViewer_t a_Mime_get_viewer(const char *content_type);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __MIME_H__ */\n"
  },
  {
    "path": "src/IO/proto.c",
    "content": "/*\n * File: proto.c\n *\n * Copyright (C) 2003-2007 Jorge Arellano Cid <jcid@dillo.org>,\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/* This module may be programmed to manage dpi-programs. */\n\n"
  },
  {
    "path": "src/IO/tls.c",
    "content": "/*\n * File: tls.c\n *\n * Update: Modified to use OpenSSL directly, without the mbed SSL dependency.\n * Original source copyright was (used as a template for the current code):\n *\n * Copyright (C) 2011 Benjamin Johnson <obeythepenguin@users.sourceforge.net>\n * (for the https code offered from dplus browser that formed the basis...)\n * Copyright 2016 corvid\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n/*\n * https://www.ssllabs.com/ssltest/viewMyClient.html\n * https://badssl.com\n *\n * Using TLS in Applications: https://datatracker.ietf.org/wg/uta/documents/\n * TLS: https://datatracker.ietf.org/wg/tls/documents/\n */\n\n#include \"config.h\"\n#include \"../msg.h\"\n\n#ifndef ENABLE_SSL\n\nvoid a_Tls_init()\n{\n   MSG(\"TLS: Disabled at compilation time.\\n\");\n}\n\n#else\n\n#include <assert.h>\n#include <errno.h>\n\n#include \"../../dlib/dlib.h\"\n#include \"../dialog.hh\"\n#include \"../klist.h\"\n#include \"iowatch.hh\"\n#include \"tls.h\"\n#include \"Url.h\"\n\n#include <openssl/err.h>\n#include <openssl/rand.h>\n#include <openssl/ssl.h>\n\n#define CERT_STATUS_NONE 0\n#define CERT_STATUS_RECEIVING 1\n#define CERT_STATUS_CLEAN 2\n#define CERT_STATUS_BAD 3\n#define CERT_STATUS_USER_ACCEPTED 4\n\ntypedef struct {\n   char *hostname;\n   int port;\n   int cert_status;\n} Server_t;\n\ntypedef struct {\n   char *name;\n   Dlist *servers;\n} CertAuth_t;\n\ntypedef struct {\n   int fd;\n   int connkey;\n} FdMapEntry_t;\n\n/*\n * Data type for TLS connection information\n */\ntypedef struct {\n   int fd;\n   DilloUrl *url;\n   SSL *ssl;\n   bool_t handshaked;\n   bool_t connecting;\n} Conn_t;\n\n/* List of active TLS connections */\nstatic Klist_t *conn_list = NULL;\n\n/* Shared TLS context */\nSSL_CTX *ssl_context = NULL;\n\nstatic bool_t ssl_enabled = TRUE;\n\nstatic Dlist *servers;\nstatic Dlist *fd_map;\n\nstatic void Tls_handshake_cb(int fd, void *vconnkey);\n\n/*\n * Compare by FD.\n */\nstatic int Tls_fd_map_cmp(const void *v1, const void *v2)\n{\n   int fd = VOIDP2INT(v2);\n   const FdMapEntry_t *e = v1;\n\n   return (fd != e->fd);\n}\n\nstatic void Tls_fd_map_add_entry(int fd, int connkey)\n{\n   FdMapEntry_t *e = dNew0(FdMapEntry_t, 1);\n   e->fd = fd;\n   e->connkey = connkey;\n\n   if (dList_find_custom(fd_map, INT2VOIDP(e->fd), Tls_fd_map_cmp)) {\n      MSG_ERR(\"TLS FD ENTRY ALREADY FOUND FOR %d\\n\", e->fd);\n      assert(0);\n   }\n\n   dList_append(fd_map, e);\n//MSG(\"ADD ENTRY %d %s\\n\", e->fd, URL_STR(sd->url));\n}\n\n/*\n * Remove and free entry from fd_map.\n */\nstatic void Tls_fd_map_remove_entry(int fd)\n{\n   void *data = dList_find_custom(fd_map, INT2VOIDP(fd), Tls_fd_map_cmp);\n\n//MSG(\"REMOVE ENTRY %d\\n\", fd);\n   if (data) {\n      dList_remove_fast(fd_map, data);\n      dFree(data);\n   } else {\n      MSG(\"TLS FD ENTRY NOT FOUND FOR %d\\n\", fd);\n   }\n}\n\n/*\n * Return TLS connection information for a given file\n * descriptor, or NULL if no TLS connection was found.\n */\nvoid *a_Tls_connection(int fd)\n{\n   Conn_t *conn;\n\n   if (fd_map) {\n      FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),\n                                            Tls_fd_map_cmp);\n\n      if (fme && (conn = a_Klist_get_data(conn_list, fme->connkey)))\n         return conn;\n   }\n   return NULL;\n}\n\n/*\n * Add a new TLS connection information node.\n */\nstatic Conn_t *Tls_conn_new(int fd, const DilloUrl *url,\n                            SSL *ssl)\n{\n   Conn_t *conn = dNew0(Conn_t, 1);\n   conn->fd = fd;\n   conn->url = a_Url_dup(url);\n   conn->ssl = ssl;\n   conn->handshaked = FALSE;\n   conn->connecting = TRUE;\n   return conn;\n}\n\nstatic int Tls_make_conn_key(Conn_t *conn)\n{\n   int key = a_Klist_insert(&conn_list, conn);\n\n   Tls_fd_map_add_entry(conn->fd, key);\n\n   return key;\n}\n\n/*\n * Load certificates from a given filename.\n */\nstatic int Tls_load_certificates_from_file(SSL_CTX *ssl_context, const char *const filename)\n{\n   int ret = SSL_CTX_load_verify_locations(ssl_context, filename, NULL);\n\n   if (ret == 0) {\n      _MSG(\"Failed to parse certificates from %s\\n\", filename);\n   }\n\n   return ret;\n}\n\n/*\n * Load certificates from a given pathname.\n */\nstatic int Tls_load_certificates_from_path(SSL_CTX *ssl_context, const char *const pathname)\n{\n   int ret = SSL_CTX_load_verify_locations(ssl_context, NULL, pathname);\n\n   if (ret == 0) {\n      _MSG(\"Failed to parse certificates from %s\\n\", pathname);\n   }\n\n   return ret;\n}\n\n/*\n * Load trusted certificates.\n */\nstatic void Tls_load_certificates(SSL_CTX *ssl_context)\n{\n   /* curl-7.37.1 says that the following bundle locations are used on \"Debian\n    * systems\", \"Redhat and Mandriva\", \"old(er) Redhat\", \"FreeBSD\", and\n    * \"OpenBSD\", respectively -- and that the /etc/ssl/certs/ path is needed on\n    * \"SUSE\". No doubt it's all changed some over time, but this gives us\n    * something to work with.\n    */\n   uint_t u;\n   char *userpath;\n   //X509 *curr;\n   int loaded = 0;\n\n   static const char *const ca_files[] = {\n      \"/etc/ssl/certs/ca-certificates.crt\",\n      \"/etc/pki/tls/certs/ca-bundle.crt\",\n      \"/usr/share/ssl/certs/ca-bundle.crt\",\n      \"/usr/local/share/certs/ca-root.crt\",\n      \"/etc/ssl/cert.pem\",\n      CA_CERTS_FILE\n   };\n\n   static const char *const ca_paths[] = {\n      \"/etc/ssl/certs/\",\n      CA_CERTS_DIR\n   };\n\n   for (u = 0; u < sizeof(ca_files)/sizeof(ca_files[0]); u++) {\n      if (*ca_files[u])\n         loaded += Tls_load_certificates_from_file(ssl_context, ca_files[u]);\n   }\n\n   for (u = 0; u < sizeof(ca_paths)/sizeof(ca_paths[0]); u++) {\n      if (*ca_paths[u]) {\n         loaded += Tls_load_certificates_from_path(ssl_context, ca_paths[u]);\n      }\n   }\n\n   userpath = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/certs/\", NULL);\n   Tls_load_certificates_from_path(ssl_context, userpath);\n   dFree(userpath);\n\n   if (loaded == 0) {\n      MSG(\"No TLS cert loaded: please check the paths of CA files.\\n\");\n   } else {\n      MSG(\"Loaded TLS certificates.\\n\");\n   }\n}\n\n/*\n * Select safe ciphersuites.\n */\nstatic void Tls_set_cipher_list(SSL_CTX *ssl_context)\n{\n\n#if 0\n\n   /* Very strict cipher list */\n   \n   const char *cipher_list = \"EECDH+AESGCM+AES128:EECDH+AESGCM+AES256:EECDH+CHACHA20:EDH+AESGCM+AES128:EDH+AESGCM+AES256:EDH+CHACHA20:EECDH+SHA256+AES128:EECDH+SHA384+AES256:EDH+SHA256+AES128:EDH+SHA256+AES256:EECDH+SHA1+AES128:EECDH+SHA1+AES256:EDH+SHA1+AES128:EDH+SHA1+AES256:EECDH+HIGH:EDH+HIGH:AESGCM+AES128:AESGCM+AES256:CHACHA20:SHA256+AES128:SHA256+AES256:SHA1+AES128:SHA1+AES256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!aECDH\";\n\n#else\n\n   /* Less strict cipher list, just exclude:\n    * eNULL, which has no encryption\n    * aNULL, which has no authentication\n    * LOW, which as of 2014 use 64 or 56-bit encryption\n    * EXPORT40, which uses 40-bit encryption\n    * RC4, for which methods were found in 2013 to defeat it somewhat too easily\n    * 3DES, not very secure nowadays\n    * MD5, broken hash function\n    * PSK, shared key algorithms\n    * KRB5, kerberos is not needed\n    * aECDH, anonymous Elliptic Curve Diffie Hellman cipher suites\n    */\n   \n   const char *cipher_list = \"ALL:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!aECDH\";\n\n#endif\n\n SSL_CTX_set_cipher_list(ssl_context, cipher_list);\n}\n\n/*\n * Initialize a new TLS context.\n */\nstatic SSL_CTX * Tls_context_new(void)\n{\n   SSL_CTX *ssl_context;\n   \n   /* Create TLS context */\n   ssl_context = SSL_CTX_new(SSLv23_client_method());\n   if (ssl_context == NULL){\n      MSG(\"Error creating SSL context\\n\");\n      return NULL;\n   }\n   \n   /* SSL2 has been known to be insecure forever, disabling SSL3 is in response\n    * to POODLE, and disabling compression is in response to CRIME. */\n   SSL_CTX_set_options(ssl_context, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_COMPRESSION);\n\n   /* Load trusted certificates */\n   Tls_load_certificates(ssl_context);\n\n   /* Set safe ciphersuites */\n   Tls_set_cipher_list(ssl_context);\n\n   return ssl_context;\n}\n\n/*\n * Initialize the TLS library.\n */\nvoid a_Tls_init(void)\n{\n   /* Initialize library */\n   SSL_load_error_strings();\n   SSL_library_init();\n\n   /* Initialize entropy */\n   if (RAND_status() != 1){\n      /*Insufficient entropy.  Deal with it?*/\n      MSG(\"Insufficient random entropy.\\n\");\n   }\n\n   /* Initialize global TLS context */\n   ssl_context = Tls_context_new();\n\n   /* Initialize global lists */\n   fd_map = dList_new(20);\n   servers = dList_new(8);\n}\n\n/*\n * Ordered comparison of servers.\n */\nstatic int Tls_servers_cmp(const void *v1, const void *v2)\n{\n   const Server_t *s1 = (const Server_t *)v1, *s2 = (const Server_t *)v2;\n   int cmp = dStrAsciiCasecmp(s1->hostname, s2->hostname);\n\n   if (!cmp)\n      cmp = s1->port - s2->port;\n   return cmp;\n}\n/*\n * Ordered comparison of server with URL.\n */\nstatic int Tls_servers_by_url_cmp(const void *v1, const void *v2)\n{\n   const Server_t *s = (const Server_t *)v1;\n   const DilloUrl *url = (const DilloUrl *)v2;\n\n   int cmp = dStrAsciiCasecmp(s->hostname, URL_HOST(url));\n\n   if (!cmp)\n      cmp = s->port - URL_PORT(url);\n   return cmp;\n}\n\n/*\n * The purpose here is to permit a single initial connection to a server.\n * Once we have the certificate, know whether we like it -- and whether the\n * user accepts it -- HTTP can run through queued sockets as normal.\n *\n * Return: TLS_CONNECT_READY or TLS_CONNECT_NOT_YET or TLS_CONNECT_NEVER.\n */\nint a_Tls_connect_ready(const DilloUrl *url)\n{\n   Server_t *s;\n   int ret = TLS_CONNECT_READY;\n\n   dReturn_val_if_fail(ssl_enabled, TLS_CONNECT_NEVER);\n\n   if ((s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp))) {\n      if (s->cert_status == CERT_STATUS_RECEIVING)\n         ret = TLS_CONNECT_NOT_YET;\n      else if (s->cert_status == CERT_STATUS_BAD)\n         ret = TLS_CONNECT_NEVER;\n\n      if (s->cert_status == CERT_STATUS_NONE)\n         s->cert_status = CERT_STATUS_RECEIVING;\n   } else {\n      s = dNew(Server_t, 1);\n\n      s->hostname = dStrdup(URL_HOST(url));\n      s->port = URL_PORT(url);\n      s->cert_status = CERT_STATUS_RECEIVING;\n      dList_insert_sorted(servers, s, Tls_servers_cmp);\n   }\n   return ret;\n}\n\nstatic int Tls_cert_status(const DilloUrl *url)\n{\n   Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);\n\n   return s ? s->cert_status : CERT_STATUS_NONE;\n}\n\n/*\n * Did we find problems with the certificate, and did the user proceed to\n * reject the connection?\n */\nstatic int Tls_user_said_no(const DilloUrl *url)\n{\n   return Tls_cert_status(url) == CERT_STATUS_BAD;\n}\n\n/*\n * Did everything seem proper with the certificate -- no warnings to\n * click through?\n */\nint a_Tls_certificate_is_clean(const DilloUrl *url)\n{\n   return Tls_cert_status(url) == CERT_STATUS_CLEAN;\n}\n\n/*\n * Generate dialog msg for expired cert.\n */\nstatic void Tls_cert_expired(const X509 *cert, Dstr *ds)\n{\n   const ASN1_TIME *notAfter = X509_get_notAfter(cert);\n\n   int year = (notAfter->data[ 0] - '0') * 10 + (notAfter->data[ 1] - '0') + 100;\n   int mon  = (notAfter->data[ 2] - '0') * 10 + (notAfter->data[ 3] - '0') - 1;\n   int mday = (notAfter->data[ 4] - '0') * 10 + (notAfter->data[ 5] - '0');\n   int hour = (notAfter->data[ 6] - '0') * 10 + (notAfter->data[ 7] - '0');\n   int min  = (notAfter->data[ 8] - '0') * 10 + (notAfter->data[ 9] - '0');\n   int sec  = (notAfter->data[10] - '0') * 10 + (notAfter->data[11] - '0');\n   \n   dStr_sprintfa(ds,\"Certificate expired at: %04d/%02d/%02d %02d:%02d:%02d.\\n\",\n                 year, mon, mday, hour, min, sec);\n}\n\n/*\n * Generate dialog msg when certificate is not for this host.\n */\nstatic void Tls_cert_cn_mismatch(const X509 *cert, Dstr *ds)\n{\n   char *subj;\n\n   dStr_append(ds, \"This host is not one of the hostnames listed on the TLS \"\n                   \"certificate that it sent:\\n\");\n\n   subj = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0);\n\n   dStr_append(ds, subj);\n   dStr_append(ds, \"\\n\");\n\n   OPENSSL_free(subj);\n}\n\n/*\n * Generate dialog msg when certificate is not trusted.\n */\nstatic void Tls_cert_trust_chain_failed(const X509 *cert, Dstr *ds)\n{\n   char *issuer;\n\n   issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0);\n\n   dStr_sprintfa(ds, \"Couldn't reach any trusted root certificate from \"\n                     \"supplied certificate. The issuer of the certificate was:\\n\"\n                     \"%s\\n\", issuer);\n\n   OPENSSL_free(issuer);\n}\n\n/*\n * Generate dialog msg when certificate start date is in the future.\n */\nstatic void Tls_cert_not_valid_yet(const X509 *cert, Dstr *ds)\n{\n   const ASN1_TIME *notBefore = X509_get_notBefore(cert);\n\n   int year = (notBefore->data[ 0] - '0') * 10 + (notBefore->data[ 1] - '0') + 100;\n   int mon  = (notBefore->data[ 2] - '0') * 10 + (notBefore->data[ 3] - '0') - 1;\n   int mday = (notBefore->data[ 4] - '0') * 10 + (notBefore->data[ 5] - '0');\n   int hour = (notBefore->data[ 6] - '0') * 10 + (notBefore->data[ 7] - '0');\n   int min  = (notBefore->data[ 8] - '0') * 10 + (notBefore->data[ 9] - '0');\n   int sec  = (notBefore->data[10] - '0') * 10 + (notBefore->data[11] - '0');\n\n   dStr_sprintfa(ds, \"Certificate validity begins in the future at: \"\n                     \"%04d/%02d/%02d %02d:%02d:%02d.\\n\",\n                     year, mon, mday, hour, min, sec);\n}\n\n#if OPENSSL_VERSION_NUMBER >= 0x10100000L\nint get_cert_algorithm(const X509 *cert)\n{\n    ASN1_OBJECT *ppkalg;\n    X509_PUBKEY *pubkey = X509_get_X509_PUBKEY(cert);\n    X509_PUBKEY_get0_param(&ppkalg, NULL, NULL, NULL, pubkey);\n    return OBJ_obj2nid(ppkalg);\n}\n#endif\n\n/*\n * Generate dialog msg when certificate hash algorithm is not accepted.\n */\nstatic void Tls_cert_bad_hash(const X509 *cert, Dstr *ds)\n{\n#if OPENSSL_VERSION_NUMBER >= 0x10100000L\n   int pkey_nid = get_cert_algorithm(cert);\n#else\n   int pkey_nid = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);\n#endif\n   const char* hash;\n\n   if (pkey_nid == NID_undef) {\n      hash = \"Unrecognized\";\n   } else {\n      hash = OBJ_nid2ln(pkey_nid);\n   }\n \n   dStr_sprintfa(ds, \"This certificate's hash algorithm is not accepted \"\n                     \"(%s).\\n\", hash);\n}\n\n/*\n * Generate dialog msg when public key algorithm (RSA, ECDSA) is not accepted.\n */\nstatic void Tls_cert_bad_pk_alg(const X509 *cert, Dstr *ds)\n{\n#if OPENSSL_VERSION_NUMBER >= 0x10100000L\n   int pubkey_algonid = get_cert_algorithm(cert);\n#else\n   int pubkey_algonid = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);\n#endif\n   const char *algoname;\n\n   if (pubkey_algonid == NID_undef) {\n      algoname = \"Unrecognized\";\n   } else {\n      algoname = OBJ_nid2ln(pubkey_algonid);\n   }\n\n   dStr_sprintfa(ds, \"This certificate's public key algorithm is not accepted \"\n                     \"(%s).\\n\", algoname);\n}\n\n/*\n * Generate dialog msg when the public key is not acceptable. As of 2016,\n * this was triggered by RSA keys below 2048 bits, if I recall correctly.\n */\nstatic void Tls_cert_bad_key(const X509 *cert, Dstr *ds)\n{\n#if OPENSSL_VERSION_NUMBER >= 0x10100000L\n   int pubkey_algonid = get_cert_algorithm(cert);\n#else\n   int pubkey_algonid = OBJ_obj2nid(cert->cert_info->key->algor->algorithm);\n#endif\n   const char *algoname;\n\n   if (pubkey_algonid == NID_undef) {\n      algoname = \"Unrecognized\";\n   } else {\n      algoname = OBJ_nid2ln(pubkey_algonid);\n   }\n\n   dStr_sprintfa(ds, \"This certificate's key is not accepted, which generally \"\n                     \"means it's too weak (%s).\\n\", algoname);\n}\n\n/*\n * Make a dialog msg containing warnings about problems with the certificate.\n */\nstatic char *Tls_make_bad_cert_msg(const X509 *cert, uint32_t flags)\n{\n   char *ret = NULL;\n   Dstr *ds = dStr_new(NULL);\n\n   switch (flags) {\n\n   case X509_V_OK:\n      /* Everything is ok */\n      break;\n\n   case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:\n      /* Either self signed and untrusted */\n      Tls_cert_cn_mismatch(cert, ds);\n      break;\n\n   case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:\n   case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:\n      Tls_cert_trust_chain_failed(cert, ds);\n      break;\n\n   case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:\n   case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:\n   case X509_V_ERR_CERT_SIGNATURE_FAILURE:\n   case X509_V_ERR_CRL_SIGNATURE_FAILURE:\n      Tls_cert_bad_hash(cert, ds);\n      break;\n\n   case X509_V_ERR_CERT_NOT_YET_VALID:\n   case X509_V_ERR_CRL_NOT_YET_VALID:\n      Tls_cert_not_valid_yet(cert, ds);\n      break;\n\n   case X509_V_ERR_CERT_HAS_EXPIRED:\n   case X509_V_ERR_CRL_HAS_EXPIRED:\n      Tls_cert_expired(cert, ds);\n      break;\n      \n   case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:\n   case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:\n   case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:\n   case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:\n      dStr_sprintfa(ds, \"Certificate formatting error (%d)\\n\", flags);\n      break;\n      \n   case X509_V_ERR_INVALID_CA:\n   case X509_V_ERR_INVALID_PURPOSE:\n   case X509_V_ERR_CERT_UNTRUSTED:\n   case X509_V_ERR_CERT_REJECTED:\n   case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:\n      Tls_cert_trust_chain_failed(cert, ds);\n      break;\n      \n   case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:\n   case X509_V_ERR_AKID_SKID_MISMATCH:\n   case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:\n      Tls_cert_trust_chain_failed(cert, ds);\n      break;\n      \n   case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:\n      Tls_cert_trust_chain_failed(cert, ds);\n      break;\n      \n   case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:\n      Tls_cert_trust_chain_failed(cert, ds);\n      break;\n      \n   default:\n      dStr_sprintfa(ds, \"The certificate can not be validated: flag value 0x%04x\", flags);\n\n   }\n   \n   ret = ds->str;\n   dStr_free(ds, 0);\n   return ret;\n}\n\nstatic int Tls_cert_auth_cmp(const void *v1, const void *v2)\n{\n   const CertAuth_t *c1 = (CertAuth_t *)v1, *c2 = (CertAuth_t *)v2;\n\n   return strcmp(c1->name, c2->name);\n}\n\nstatic int Tls_cert_auth_cmp_by_name(const void *v1, const void *v2)\n{\n   const CertAuth_t *c = (CertAuth_t *)v1;\n   const char *name = (char *)v2;\n\n   return strcmp(c->name, name);\n}\n\n/*\n * Examine the certificate, and, if problems are detected, ask the user what\n * to do.\n * Return: -1 if connection should be canceled, or 0 if it should continue.\n */\nstatic int Tls_examine_certificate(SSL *ssl_connection, Server_t *srv)\n{\n   X509 *cert;\n   uint32_t st;\n   int choice = -1, ret = -1;\n   char *title = dStrconcat(\"Dillo TLS security warning: \",srv->hostname,NULL);\n\n   cert = SSL_get_peer_certificate(ssl_connection);\n   if (cert == NULL){\n      /* Inform user that remote system cannot be trusted */\n      choice = a_Dialog_choice(title,\n         \"No certificate received from this site. Can't verify who it is.\",\n         \"Continue\", \"Cancel\", NULL);\n\n      /* Abort on anything but \"Continue\" */\n      if (choice == 1){\n         ret = 0;\n      }\n   } else {\n      /* check the certificate */\n      st = SSL_get_verify_result(ssl_connection);\n      if (st == X509_V_OK) {\n\t ret = 0;\n      } else {\n         char *dialog_warning_msg = Tls_make_bad_cert_msg(cert, st);\n\n         choice = a_Dialog_choice(title, dialog_warning_msg, \"Continue\",\n                                  \"Cancel\", NULL);\n         if (choice == 1) {\n            ret = 0;\n         }\n         dFree(dialog_warning_msg);\n      }\n   }\n   dFree(title);\n\n   X509_free(cert);\n\n   if (choice == -1) {\n      srv->cert_status = CERT_STATUS_CLEAN;          /* no warning popups */\n   } else if (choice == 1) {\n      srv->cert_status = CERT_STATUS_USER_ACCEPTED;  /* clicked Continue */\n   } else {\n      /* 2 for Cancel, or 0 when window closed. */\n      srv->cert_status = CERT_STATUS_BAD;\n   }\n   return ret;\n}\n\n/*\n * If the connection was closed before we got the certificate, we need to\n * reset state so that we'll try again.\n */\nvoid a_Tls_reset_server_state(const DilloUrl *url)\n{\n   if (servers) {\n      Server_t *s = dList_find_sorted(servers, url, Tls_servers_by_url_cmp);\n\n      if (s && s->cert_status == CERT_STATUS_RECEIVING)\n         s->cert_status = CERT_STATUS_NONE;\n   }\n}\n\n/*\n * Close an open TLS connection.\n */\nstatic void Tls_close_by_key(int connkey)\n{\n   Conn_t *c;\n\n   if ((c = a_Klist_get_data(conn_list, connkey))) {\n      a_Tls_reset_server_state(c->url);\n      if (c->connecting) {\n         a_IOwatch_remove_fd(c->fd, -1);\n         dClose(c->fd);\n      }\n\n      if(c->ssl != NULL) {\n\t SSL_free(c->ssl);\n\t c->ssl = NULL;\n      }\n\n      a_Url_free(c->url);\n      Tls_fd_map_remove_entry(c->fd);\n      a_Klist_remove(conn_list, connkey);\n      dFree(c);\n   }\n}\n\n/*\n * Connect, set a callback if it's still not completed. If completed, check\n * the certificate and report back to http.\n */\nstatic void Tls_handshake(int fd, int connkey)\n{\n   int ret;\n   bool_t ongoing = FALSE, failed = TRUE;\n   Conn_t *conn;\n\n   if (!(conn = a_Klist_get_data(conn_list, connkey))) {\n      MSG(\"Tls_connect: conn for fd %d not valid\\n\", fd);\n      return;\n   }\n\n   if (!conn->handshaked) {\n      ret = SSL_connect(conn->ssl);\n\n      if (ret == -1 && (SSL_get_error(conn->ssl, ret) == SSL_ERROR_WANT_READ ||\n\t\t\tSSL_get_error(conn->ssl, ret) == SSL_ERROR_WANT_WRITE)) {\n\t int err = SSL_get_error(conn->ssl, ret);\n         int want = err == SSL_ERROR_WANT_READ ? DIO_READ : DIO_WRITE;\n\n         _MSG(\"iowatching fd %d for tls -- want %s\\n\", fd,\n\t      err == SSL_ERROR_WANT_READ ? \"read\" : \"write\");\n         a_IOwatch_remove_fd(fd, -1);\n         a_IOwatch_add_fd(fd, want, Tls_handshake_cb, INT2VOIDP(connkey));\n         ongoing = TRUE;\n         failed = FALSE;\n      } else if (ret == 1) {\n\t conn->handshaked = TRUE;\n         Server_t *srv = dList_find_sorted(servers, conn->url,\n                                           Tls_servers_by_url_cmp);\n         if (srv->cert_status == CERT_STATUS_RECEIVING) {\n            /* Making first connection with the server. Show cipher used. */\n            SSL *ssl = conn->ssl;\n            const char *version = SSL_get_version(ssl),\n                       *cipher = SSL_get_cipher_list(ssl, 0);\n\n            MSG(\"%s\", URL_AUTHORITY(conn->url));\n            if (URL_PORT(conn->url) != URL_HTTPS_PORT)\n               MSG(\":%d\", URL_PORT(conn->url));\n            MSG(\" %s, cipher %s\\n\", version, cipher);\n         }\n         if (srv->cert_status == CERT_STATUS_USER_ACCEPTED ||\n             (Tls_examine_certificate(conn->ssl, srv) != -1)) {\n            failed = FALSE;\n         }\n      } else if (ret < 0) {\n         int err = SSL_get_error(conn->ssl, ret);\n         MSG(\"SSL_connect() failed with error %d.\\n\", err);\n      } else {\n         MSG(\"SSL_connect() failed.\\n\");\n      }\n   }\n\n   /*\n    * If there were problems with the certificate, the connection may have\n    * been closed by the server if the user responded too slowly to a popup.\n    */\n\n   if (!ongoing) {\n      if (a_Klist_get_data(conn_list, connkey)) {\n         conn->connecting = FALSE;\n         if (failed) {\n            Tls_close_by_key(connkey);\n         }\n         a_IOwatch_remove_fd(fd, -1);\n         a_Http_connect_done(fd, failed ? FALSE : TRUE);\n      } else {\n         MSG(\"Connection disappeared. Too long with a popup popped up?\\n\");\n      }\n   }\n}\n\nstatic void Tls_handshake_cb(int fd, void *vconnkey)\n{\n   Tls_handshake(fd, VOIDP2INT(vconnkey));\n}\n\n/*\n * Make TLS connection over a connect()ed socket.\n */\nvoid a_Tls_connect(int fd, const DilloUrl *url)\n{\n   SSL *ssl = SSL_new(ssl_context);\n   bool_t success = TRUE;\n   int connkey = -1;\n\n   if (ssl == NULL) {\n      MSG(\"Error creating SSL connection\\n\");\n      success = FALSE;\n   }\n   \n   if (!ssl_enabled)\n      success = FALSE;\n\n   if (success && Tls_user_said_no(url)) {\n      success = FALSE;\n   }\n\n   /* Need to do this if we want to have the option of dealing\n    * with self-signed certs */\n   if (success) {\n      SSL_set_verify(ssl, SSL_VERIFY_NONE, 0);\n   }\n\n   /* Assign TLS connection to this file descriptor */\n   if (success) {\n      Conn_t *conn = Tls_conn_new(fd, url, ssl);\n      connkey = Tls_make_conn_key(conn);\n\n      if (SSL_set_fd(ssl, fd) == 0) {\n         MSG(\"Error connecting network socket to SSL.\\n\");\n         success = FALSE;\n     }\n   }\n   \n   /* Configure SSL to use the servername */\n   if (success && SSL_set_tlsext_host_name(ssl, URL_HOST(url)) == 0) {\n      MSG(\"Error setting servername to SSL\\n\");\n      success = FALSE;\n   }\n\n   /*MSG(\"TLS connection initialized, trying to handshake.\\n\");*/\n   \n   if (!success) {\n      a_Tls_reset_server_state(url);\n      a_Http_connect_done(fd, success);\n   } else {\n      Tls_handshake(fd, connkey);\n   }\n}\n\n/*\n * Read data from an open TLS connection.\n */\nint a_Tls_read(void *conn, void *buf, size_t len)\n{\n   Conn_t *c = (Conn_t*)conn;\n   int ret = SSL_read(c->ssl, buf, len);\n\n   if (ret < 0) {\n      int err = SSL_get_error(c->ssl, ret);\n      if (err != SSL_ERROR_WANT_READ && err != SSL_ERROR_WANT_WRITE)\n\t MSG(\"READ failed with %d: a TLS error\\n\", err);\n   }\n   return ret;\n}\n\n/*\n * Write data to an open TLS connection.\n */\nint a_Tls_write(void *conn, void *buf, size_t len)\n{\n   Conn_t *c = (Conn_t*)conn;\n   int ret = SSL_write(c->ssl, buf, len);\n\n   if (ret < 0) {\n      MSG(\"WRITE failed with %d: a TLS error\\n\", SSL_get_error(c->ssl, ret));\n   }\n   return ret;\n}\n\nvoid a_Tls_close_by_fd(int fd)\n{\n   FdMapEntry_t *fme = dList_find_custom(fd_map, INT2VOIDP(fd),\n                                         Tls_fd_map_cmp);\n\n   if (fme) {\n      Tls_close_by_key(fme->connkey);\n   }\n}\n\nstatic void Tls_servers_freeall()\n{\n   if (servers) {\n      Server_t *s;\n      int i, n = dList_length(servers);\n\n      for (i = 0; i < n; i++) {\n         s = (Server_t *) dList_nth_data(servers, i);\n         dFree(s->hostname);\n         dFree(s);\n      }\n      dList_free(servers);\n   }\n}\n\nstatic void Tls_fd_map_remove_all()\n{\n   if (fd_map) {\n      FdMapEntry_t *fme;\n      int i, n = dList_length(fd_map);\n\n      for (i = 0; i < n; i++) {\n         fme = (FdMapEntry_t *) dList_nth_data(fd_map, i);\n         dFree(fme);\n      }\n      dList_free(fd_map);\n   }\n}\n\n/*\n * Clean up\n */\nvoid a_Tls_freeall(void)\n{\n   Tls_fd_map_remove_all();\n   Tls_servers_freeall();\n}\n\n#endif /* ENABLE_SSL */\n"
  },
  {
    "path": "src/IO/tls.h",
    "content": "#ifndef __TLS_H__\n#define __TLS_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"../url.h\"\n\n#define TLS_CONNECT_NEVER -1\n#define TLS_CONNECT_NOT_YET 0\n#define TLS_CONNECT_READY 1\n\nvoid a_Tls_init();\n\n\n#ifdef ENABLE_SSL\nint a_Tls_certificate_is_clean(const DilloUrl *url);\nint a_Tls_connect_ready(const DilloUrl *url);\nvoid a_Tls_reset_server_state(const DilloUrl *url);\n\n/* Use to initiate a TLS connection. */\nvoid a_Tls_connect(int fd, const DilloUrl *url);\n\nvoid *a_Tls_connection(int fd);\n\nvoid a_Tls_freeall();\n\nvoid a_Tls_close_by_fd(int fd);\nint a_Tls_read(void *conn, void *buf, size_t len);\nint a_Tls_write(void *conn, void *buf, size_t len);\n#else\n\n#define a_Tls_certificate_is_clean(host) 0\n#define a_Tls_connect_ready(url) TLS_CONNECT_NEVER\n#define a_Tls_reset_server_state(url) ;\n#define a_Tls_handshake(fd, url) ;\n#define a_Tls_connect(fd, url) ;\n#define a_Tls_connection(fd) NULL\n#define a_Tls_freeall() ;\n#define a_Tls_close_by_fd(fd) ;\n#define a_Tls_read(conn, buf, len) 0\n#define a_Tls_write(conn, buf, len) 0\n#endif\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* __TLS_H__ */\n\n"
  },
  {
    "path": "src/Makefile",
    "content": "include ../Makefile.options\n\nCXXFLAGS_EXTRA = -DDILLO_SYSCONF='\"$(DILLO_ETCDIR)\"' -DDOC_PATH='\"$(DOC_PATH)\"' -DBINNAME='\"$(BINNAME)\"'\n\nall: recurse_into_IO $(BINNAME)\n\nrecurse_into_IO:\n\t@echo \"Making all in IO\"\n\t@(cd IO; make)\n\n$(BINNAME).o: $(BINNAME).cc\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c $(BINNAME).cc\n\npaths.o: paths.cc paths.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c paths.cc\n\ntipwin.o: tipwin.cc tipwin.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c tipwin.cc\n\nui.o: ui.cc ui.hh pixmaps.h\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c -o ui.o ui.cc\n\nuicmd.o: uicmd.cc uicmd.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c uicmd.cc\n\nbw.o: bw.c bw.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c bw.c\n\ncookies.o: cookies.c cookies.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c cookies.c\n\nauth.o: auth.c auth.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c auth.c\n\nmd5.o: md5.c md5.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c md5.c\n\ndigest.o: digest.c digest.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c digest.c\n\ncolors.o: colors.c colors.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c colors.c\n\nmisc.o: misc.c misc.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c misc.c\n\nhistory.o: history.c history.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c history.c\n\nhsts.o: hsts.c hsts.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c hsts.c\n\nprefs.o: prefs.c\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c prefs.c\n\nprefsparser.o: prefsparser.cc prefsparser.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c prefsparser.cc\n\nkeys.o: keys.cc keys.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c keys.cc\n\nurl.o: url.c url.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c url.c\n\nbitvec.o: bitvec.c bitvec.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c bitvec.c\n\nklist.o: klist.c klist.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c klist.c\n\nchain.o: chain.c chain.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c chain.c\n\nutf8.o: utf8.cc utf8.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c utf8.cc\n\ntimeout.o: timeout.cc timeout.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c timeout.cc\n\ndialog.o: dialog.cc dialog.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c dialog.cc\n\nweb.o: web.cc web.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c web.cc\n\nnav.o: nav.c nav.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c nav.c\n\ncache.o: cache.c cache.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c cache.c\n\ndecode.o: decode.c decode.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c decode.c\n\ndicache.o: dicache.c dicache.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c dicache.c\n\ncapi.o: capi.c capi.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c capi.c\n\ndomain.o: domain.c domain.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c domain.c\n\ncss.o: css.cc css.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c css.cc\n\ncssparser.o: cssparser.cc cssparser.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c cssparser.cc\n\nstyleengine.o: styleengine.cc styleengine.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c styleengine.cc\n\nplain.o: plain.cc\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c plain.cc\n\nhtml.o: html.cc html.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c html.cc\n\nform.o: form.cc form.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c form.cc\n\ntable.o: table.cc table.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c table.cc\n\nbookmark.o: bookmark.c bookmark.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c bookmark.c\n\ndns.o: dns.c dns.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c dns.c\n\ngif.o: gif.c\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c gif.c\n\njpeg.o: jpeg.c\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c jpeg.c\n\npng.o: png.c\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c png.c\n\nimgbuf.o: imgbuf.cc imgbuf.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c imgbuf.cc\n\nimage.o: image.cc image.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c image.cc\n\nmenu.o: menu.cc menu.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c menu.cc\n\ndpiapi.o: dpiapi.c dpiapi.h\n\t$(COMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CFLAGS) $(LIBPNG16_CXXFLAGS) -c dpiapi.c\n\nfindbar.o: findbar.cc findbar.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c findbar.cc\n\nxembed.o: xembed.cc xembed.hh\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) -c xembed.cc\n\n\n$(BINNAME): $(BINNAME).o paths.o tipwin.o ui.o uicmd.o bw.o cookies.o auth.o md5.o digest.o colors.o misc.o history.o hsts.o prefs.o prefsparser.o keys.o url.o bitvec.o klist.o chain.o utf8.o timeout.o dialog.o web.o nav.o cache.o decode.o dicache.o capi.o domain.o css.o cssparser.o styleengine.o plain.o html.o form.o table.o bookmark.o dns.o gif.o jpeg.o png.o imgbuf.o image.o menu.o dpiapi.o findbar.o xembed.o ../dlib/libDlib.a ../dpip/libDpip.a IO/libDiof.a ../dw/libDw-widgets.a ../dw/libDw-fltk.a ../dw/libDw-core.a ../lout/liblout.a\n\t$(CXXCOMPILE) $(CXXFLAGS_EXTRA) $(LIBFLTK_CXXFLAGS) $(LIBPNG16_CXXFLAGS) $(LDFLAGS) $(DILLO_LDFLAGS) $(HTTPS_LDFLAGS) -o $(BINNAME) $(BINNAME).o paths.o tipwin.o ui.o uicmd.o bw.o cookies.o auth.o md5.o digest.o colors.o misc.o history.o hsts.o prefs.o prefsparser.o keys.o url.o bitvec.o klist.o chain.o utf8.o timeout.o dialog.o web.o nav.o cache.o decode.o dicache.o capi.o domain.o css.o cssparser.o styleengine.o plain.o html.o form.o table.o bookmark.o dns.o gif.o jpeg.o png.o imgbuf.o image.o menu.o dpiapi.o findbar.o xembed.o ../dlib/libDlib.a ../dpip/libDpip.a IO/libDiof.a ../dw/libDw-widgets.a ../dw/libDw-fltk.a ../dw/libDw-core.a ../lout/liblout.a\n\nclean:\n\trm -f *.o *.a $(BINNAME)\n\t@echo \"Making clean in IO\"\n\t@(cd IO; make clean)\n\ninstall: all\n\t$(INSTALL_SH) -c -d \"$(DILLO_BINDIR)\"\n\t$(INSTALL) -c $(BINNAME) \"$(DILLO_BINDIR)\"\n\nuninstall:\n\trm -f \"$(DILLO_BINDIR)/$(BINNAME)\"\n"
  },
  {
    "path": "src/auth.c",
    "content": "/*\n * File: auth.c\n *\n * Copyright 2008 Jeremy Henty   <onepoint@starurchin.org>\n * Copyright 2009 Justus Winter  <4winter@informatik.uni-hamburg.de>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/* Handling of HTTP AUTH takes place here.\n * This implementation aims to follow RFC 2617:\n * http://www.ietf.org/rfc/rfc2617.txt\n */\n\n\n#include <ctype.h> /* iscntrl */\n#include \"auth.h\"\n#include \"msg.h\"\n#include \"misc.h\"\n#include \"dialog.hh\"\n#include \"digest.h\"\n#include \"../dlib/dlib.h\"\n\ntypedef struct {\n   int ok;\n   enum AuthParseHTTPAuthType_t type;\n   const char *realm;\n   const char *nonce;\n   const char *opaque;\n   int stale;\n   enum AuthParseDigestAlgorithm_t algorithm;\n   const char *domain;\n   enum AuthParseDigestQOP_t qop;\n} AuthParse_t;\n\ntypedef struct {\n   char *scheme;\n   char *authority;\n   Dlist *realms;\n} AuthHost_t;\n\ntypedef struct {\n   const AuthParse_t *auth_parse;\n   const DilloUrl *url;\n} AuthDialogData_t;\n\n/*\n *  Local data\n */\nstatic Dlist *auth_hosts;\n\n/*\n * Initialize the auth module.\n */\nvoid a_Auth_init(void)\n{\n   auth_hosts = dList_new(1);\n}\n\nstatic AuthParse_t *Auth_parse_new()\n{\n   AuthParse_t *auth_parse = dNew(AuthParse_t, 1);\n   auth_parse->ok = 0;\n   auth_parse->type = TYPENOTSET;\n   auth_parse->realm = NULL;\n   auth_parse->nonce = NULL;\n   auth_parse->opaque = NULL;\n   auth_parse->stale = 0;\n   auth_parse->algorithm = ALGORITHMNOTSET;\n   auth_parse->domain = NULL;\n   auth_parse->qop = QOPNOTSET;\n   return auth_parse;\n}\n\nstatic void Auth_parse_free(AuthParse_t *auth_parse)\n{\n   if (auth_parse) {\n      dFree((void *)auth_parse->realm);\n      dFree((void *)auth_parse->nonce);\n      dFree((void *)auth_parse->opaque);\n      dFree((void *)auth_parse->domain);\n      dFree(auth_parse);\n   }\n}\n\nstatic int Auth_path_is_inside(const char *path1, const char *path2, int len)\n{\n   /*\n    * path2 is effectively truncated to length len.  Typically len will be\n    * strlen(path2), or 1 less when we want to ignore a trailing '/'.\n    */\n   return\n      strncmp(path1, path2, len) == 0 &&\n      (path1[len] == '\\0' || path1[len] == '/');\n}\n\n/*\n * Check valid chars.\n * Return: 0 if invalid, 1 otherwise.\n */\nstatic int Auth_is_token_char(char c)\n{\n   const char *invalid = \"\\\"()<>@,;:\\\\[]?=/{} \\t\";\n   return (!isascii(c) || strchr(invalid, c) || iscntrl((uchar_t)c)) ? 0 : 1;\n}\n\n/*\n * Unquote the content of a (potentially) quoted string.\n * Return: newly allocated unquoted content.\n *\n * Arguments:\n * valuep: pointer to a pointer to the first char.\n *\n * Preconditions:\n * *valuep points to a correctly quoted and escaped string.\n *\n * Postconditions:\n * *valuep points to the first not processed char.\n *\n */\nstatic Dstr *Auth_unquote_value(char **valuep)\n{\n   char c, quoted;\n   char *value = *valuep;\n   Dstr *result;\n\n   while (*value == ' ' || *value == '\\t')\n      value++;\n\n   if ((quoted = *value == '\"'))\n      value++;\n\n   result = dStr_new(NULL);\n   while ((c = *value) &&\n          (( quoted && c != '\"') ||\n           (!quoted && Auth_is_token_char(c)))) {\n      dStr_append_c(result, (c == '\\\\' && value[1]) ? *++value : c);\n      value++;\n   }\n\n   if (quoted && *value == '\\\"')\n      value++;\n   *valuep = value;\n   return result;\n}\n\ntypedef int (Auth_parse_token_value_callback_t)(AuthParse_t *auth_parse,\n                                                char *token,\n                                                const char *value);\n\n\n/*\n * Parse authentication challenge into token-value pairs\n * and feed them into the callback function.\n *\n * The parsing is aborted should the callback function return 0.\n *\n * Return: 1 if the parse succeeds, 0 otherwise.\n */\nstatic int Auth_parse_token_value(AuthParse_t *auth_parse, char **auth,\n                                  Auth_parse_token_value_callback_t *callback)\n{\n   char keep_going, expect_quoted;\n   char *token, *beyond_token;\n   Dstr *value;\n   size_t *token_size;\n\n   while (**auth) {\n      _MSG(\"Auth_parse_token_value: remaining: %s\\n\", *auth);\n\n     /* parse a token */\n      token = *auth;\n\n      token_size = 0;\n      while (Auth_is_token_char(**auth)) {\n         (*auth)++;\n         token_size++;\n      }\n      if (token_size == 0) {\n         MSG(\"Auth_parse_token_value: missing auth token\\n\");\n         return 0;\n      }\n      beyond_token = *auth;\n      /* skip linear whitespace characters */\n      while (**auth == ' ' || **auth == '\\t')\n         (*auth)++;\n\n      /* parse the '=' */\n      switch (*(*auth)++) {\n      case '=':\n         *beyond_token = '\\0';\n         break;\n      case '\\0':\n      case ',':\n         MSG(\"Auth_parse_token_value: missing auth token value\\n\");\n         return 0;\n         break;\n      default:\n         MSG(\"Auth_parse_token_value: garbage after auth token\\n\");\n         return 0;\n         break;\n      }\n\n      value = Auth_unquote_value(auth);\n      expect_quoted = !(strcmp(token, \"stale\") == 0 ||\n                        strcmp(token, \"algorithm\") == 0);\n\n      if (((*auth)[-1] == '\"') != expect_quoted)\n         MSG_WARN(\"Auth_parse_token_value: \"\n                  \"Values for key %s should%s be quoted.\\n\",\n                  token, expect_quoted ? \"\" : \" not\");\n\n      keep_going = callback(auth_parse, token, value->str);\n      dStr_free(value, 1);\n      if (!keep_going)\n         break;\n\n      /* skip ' ' and ',' */\n      while ((**auth == ' ') || (**auth == ','))\n         (*auth)++;\n   }\n   return 1;\n}\n\nstatic int Auth_parse_basic_challenge_cb(AuthParse_t *auth_parse, char *token,\n                                         const char *value)\n{\n   if (dStrAsciiCasecmp(\"realm\", token) == 0) {\n      if (!auth_parse->realm)\n         auth_parse->realm = strdup(value);\n      return 0; /* end parsing */\n   } else\n      MSG(\"Auth_parse_basic_challenge_cb: Ignoring unknown parameter: %s = \"\n          \"'%s'\\n\", token, value);\n   return 1;\n}\n\nstatic int Auth_parse_digest_challenge_cb(AuthParse_t *auth_parse, char *token,\n                                          const char *value)\n{\n   const char *const fn = \"Auth_parse_digest_challenge_cb\";\n\n   if (!dStrAsciiCasecmp(\"realm\", token) && !auth_parse->realm)\n      auth_parse->realm = strdup(value);\n   else if (!strcmp(\"domain\", token) && !auth_parse->domain)\n      auth_parse->domain = strdup(value);\n   else if (!strcmp(\"nonce\", token)  && !auth_parse->nonce)\n      auth_parse->nonce = strdup(value);\n   else if (!strcmp(\"opaque\", token) && !auth_parse->opaque)\n      auth_parse->opaque = strdup(value);\n   else if (strcmp(\"stale\", token) == 0) {\n      if (dStrAsciiCasecmp(\"true\", value) == 0)\n         auth_parse->stale = 1;\n      else if (dStrAsciiCasecmp(\"false\", value) == 0)\n         auth_parse->stale = 0;\n      else {\n         MSG(\"%s: Invalid stale value: %s\\n\", fn, value);\n         return 0;\n      }\n   } else if (strcmp(\"algorithm\", token) == 0) {\n      if (strcmp(\"MD5\", value) == 0)\n         auth_parse->algorithm = MD5;\n      else if (strcmp(\"MD5-sess\", value) == 0) {\n         /* auth_parse->algorithm = MD5SESS; */\n         MSG(\"%s: MD5-sess algorithm disabled (not tested because 'not \"\n             \"correctly implemented yet' in Apache 2.2)\\n\", fn);\n         return 0;\n      } else {\n         MSG(\"%s: Unknown algorithm: %s\\n\", fn, value);\n         return 0;\n      }\n   } else if (strcmp(\"qop\", token) == 0) {\n      while (*value) {\n         int len = strcspn(value, \", \\t\");\n         if (len == 4 && strncmp(\"auth\", value, 4) == 0) {\n            auth_parse->qop = AUTH;\n            break;\n         }\n         if (len == 8 && strncmp(\"auth-int\", value, 8) == 0) {\n            /* auth_parse->qop = AUTHINT; */\n            /* Keep searching; maybe we'll find an \"auth\" yet. */\n            MSG(\"%s: auth-int qop disabled (not tested because 'not \"\n                \"implemented yet' in Apache 2.2)\\n\", fn);\n         } else {\n            MSG(\"%s: Unknown qop value in %s\\n\", fn, value);\n         }\n         value += len;\n         while (*value == ' ' || *value == '\\t')\n            value++;\n         if (*value == ',')\n            value++;\n         while (*value == ' ' || *value == '\\t')\n            value++;\n      }\n   } else {\n      MSG(\"%s: Ignoring unknown parameter: %s = '%s'\\n\", fn, token, value);\n   }\n   return 1;\n}\n\nstatic void Auth_parse_challenge_args(AuthParse_t *auth_parse,\n                                      char **challenge,\n                                      Auth_parse_token_value_callback_t *cb)\n{\n   /* parse comma-separated token-value pairs */\n   while (1) {\n      /* skip space and comma characters */\n      while (**challenge == ' ' || **challenge == ',')\n         (*challenge)++;\n      /* end of string? */\n      if (!**challenge)\n         break;\n      /* parse token-value pair */\n      if (!Auth_parse_token_value(auth_parse, challenge, cb))\n         break;\n   }\n\n   if (auth_parse->type == BASIC) {\n      if (auth_parse->realm) {\n         auth_parse->ok = 1;\n      } else {\n         MSG(\"Auth_parse_challenge_args: missing Basic auth realm\\n\");\n         return;\n      }\n   } else if (auth_parse->type == DIGEST) {\n      if (auth_parse->realm && auth_parse->nonce) {\n         auth_parse->ok = 1;\n      } else {\n         MSG(\"Auth_parse_challenge_args: Digest challenge incomplete\\n\");\n         return;\n      }\n   }\n}\n\nstatic void Auth_parse_challenge(AuthParse_t *auth_parse, char *challenge)\n{\n   Auth_parse_token_value_callback_t *cb;\n\n   MSG(\"auth.c: Auth_parse_challenge: challenge = '%s'\\n\", challenge);\n   if (auth_parse->type == DIGEST) {\n      challenge += 7;\n      cb = Auth_parse_digest_challenge_cb;\n   } else {\n      challenge += 6;\n      cb = Auth_parse_basic_challenge_cb;\n   }\n   Auth_parse_challenge_args(auth_parse, &challenge, cb);\n}\n\n/*\n * Return the host that contains a URL, or NULL if there is no such host.\n */\nstatic AuthHost_t *Auth_host_by_url(const DilloUrl *url)\n{\n   AuthHost_t *host;\n   int i;\n\n   for (i = 0; (host = dList_nth_data(auth_hosts, i)); i++)\n      if (((dStrAsciiCasecmp(URL_SCHEME(url), host->scheme) == 0) &&\n           (dStrAsciiCasecmp(URL_AUTHORITY(url), host->authority) == 0)))\n         return host;\n\n   return NULL;\n}\n\n/*\n * Search all realms for the one with the given name.\n */\nstatic AuthRealm_t *Auth_realm_by_name(const AuthHost_t *host,\n                                           const char *name)\n{\n   AuthRealm_t *realm;\n   int i;\n\n   for (i = 0; (realm = dList_nth_data(host->realms, i)); i++)\n      if (strcmp(realm->name, name) == 0)\n         return realm;\n\n   return NULL;\n}\n\n/*\n * Search all realms for the one with the best-matching path.\n */\nstatic AuthRealm_t *Auth_realm_by_path(const AuthHost_t *host,\n                                       const char *path)\n{\n   AuthRealm_t *realm_best, *realm;\n   int i, j;\n   int match_length = 0;\n\n   realm_best = NULL;\n   for (i = 0; (realm = dList_nth_data(host->realms, i)); i++) {\n      char *realm_path;\n\n      for (j = 0; (realm_path = dList_nth_data(realm->paths, j)); j++) {\n         int realm_path_length = strlen(realm_path);\n         if (Auth_path_is_inside(path, realm_path, realm_path_length) &&\n             !(realm_best && match_length >= realm_path_length)) {\n            realm_best = realm;\n            match_length = realm_path_length;\n         }\n      }\n   }\n\n   return realm_best;\n}\n\nstatic void Auth_realm_delete(AuthRealm_t *realm)\n{\n   int i;\n\n   MSG(\"Auth_realm_delete: \\\"%s\\\"\\n\", realm->name);\n   for (i = dList_length(realm->paths) - 1; i >= 0; i--)\n      dFree(dList_nth_data(realm->paths, i));\n   dList_free(realm->paths);\n   dFree(realm->name);\n   dFree(realm->username);\n   dFree(realm->authorization);\n   dFree(realm->cnonce);\n   dFree(realm->nonce);\n   dFree(realm->opaque);\n   dFree(realm->domain);\n   dFree(realm);\n}\n\nstatic int Auth_realm_includes_path(const AuthRealm_t *realm, const char *path)\n{\n   int i;\n   char *realm_path;\n\n   for (i = 0; (realm_path = dList_nth_data(realm->paths, i)); i++)\n      if (Auth_path_is_inside(path, realm_path, strlen(realm_path)))\n         return 1;\n\n   return 0;\n}\n\nstatic void Auth_realm_add_path(AuthRealm_t *realm, const char *path)\n{\n   int len, i;\n   char *realm_path, *n_path;\n\n   n_path = dStrdup(path);\n   len = strlen(n_path);\n\n   /* remove trailing '/' */\n   if (len && n_path[len - 1] == '/')\n      n_path[--len] = 0;\n\n   /* delete existing paths that are inside the new one */\n   for (i = 0; (realm_path = dList_nth_data(realm->paths, i)); i++) {\n      if (Auth_path_is_inside(realm_path, path, len)) {\n         dList_remove_fast(realm->paths, realm_path);\n         dFree(realm_path);\n         i--; /* reconsider this slot */\n      }\n   }\n\n   dList_append(realm->paths, n_path);\n}\n\n/*\n * Return the authorization header for an HTTP query.\n * request_uri is a separate argument because we want it precisely as\n *   formatted in the request.\n */\nchar *a_Auth_get_auth_str(const DilloUrl *url, const char *request_uri)\n{\n   char *ret = NULL;\n   AuthHost_t *host;\n   AuthRealm_t *realm;\n\n   if ((host = Auth_host_by_url(url)) &&\n       (realm = Auth_realm_by_path(host, URL_PATH(url)))) {\n      if (realm->type == BASIC)\n         ret = dStrdup(realm->authorization);\n      else if (realm->type == DIGEST)\n         ret = a_Digest_authorization_hdr(realm, url, request_uri);\n      else\n         MSG(\"a_Auth_get_auth_str() got an unknown realm type: %i.\\n\",\n             realm->type);\n   }\n   return ret;\n}\n\n/*\n * Determine whether the user needs to authenticate.\n */\nstatic int Auth_do_auth_required(const AuthParse_t *auth_parse,\n                                 const DilloUrl *url)\n{\n   /*\n    * TO DO: I dislike the way that this code must decide whether we\n    * sent authentication during the request and trust us to resend it\n    * after the reload.  Could it be more robust if every DilloUrl\n    * recorded its authentication, and whether it was accepted?  (JCH)\n    */\n\n   AuthHost_t *host;\n   AuthRealm_t *realm;\n\n   /*\n    * The size of the following comments reflects the concerns in the\n    * TO DO at the top of this function.  It should not be so hard to\n    * explain why code is correct! (JCH)\n    */\n\n   /*\n    * If we have authentication but did not send it (because we did\n    * not know this path was in the realm) then we update the realm.\n    * We do not re-authenticate because our authentication is probably\n    * OK.  Thanks to the updated realm the forthcoming reload will\n    * make us send the authentication.  If our authentication is not\n    * OK the server will challenge us again after the reload and then\n    * we will re-authenticate.\n    */\n   if ((host = Auth_host_by_url(url)) &&\n       (realm = Auth_realm_by_name(host, auth_parse->realm))) {\n      if (!Auth_realm_includes_path(realm, URL_PATH(url))) {\n         _MSG(\"Auth_do_auth_required: updating realm '%s' with URL '%s'\\n\",\n              auth_parse->realm, URL_STR(url));\n         Auth_realm_add_path(realm, URL_PATH(url));\n         return 0;\n      }\n\n      if (auth_parse->type == DIGEST && auth_parse->stale) {\n         /* we do have valid credentials but our nonce is old */\n         dFree((void *)realm->nonce);\n         realm->nonce = dStrdup(auth_parse->nonce);\n         return 0;\n      }\n   }\n\n   /*\n    * Either we had no authentication or we sent it and the server\n    * rejected it, so we must re-authenticate.\n    */\n   return 1;\n}\n\nstatic void Auth_do_auth_dialog_cb(const char *user, const char *password,\n                                   void *vData)\n{\n   AuthDialogData_t *data;\n   AuthHost_t *host;\n   AuthRealm_t *realm;\n\n   data = (AuthDialogData_t *)vData;\n\n   /* find or create the host */\n   if (!(host = Auth_host_by_url(data->url))) {\n      /* create a new host */\n      host = dNew(AuthHost_t, 1);\n      host->scheme = dStrdup(URL_SCHEME(data->url));\n      host->authority = dStrdup(URL_AUTHORITY(data->url));\n      host->realms = dList_new(1);\n      dList_append(auth_hosts, host);\n   }\n\n   /* find or create the realm */\n   if (!(realm = Auth_realm_by_name(host, data->auth_parse->realm))) {\n      realm = dNew0(AuthRealm_t, 1);\n      realm->name = dStrdup(data->auth_parse->realm);\n      realm->paths = dList_new(1);\n      dList_append(host->realms, realm);\n   }\n   realm->type = data->auth_parse->type;\n   dFree(realm->authorization);\n   realm->authorization = NULL;\n\n   Auth_realm_add_path(realm, URL_PATH(data->url));\n\n   if (realm->type == BASIC) {\n      char *user_password = dStrconcat(user, \":\", password, NULL);\n      char *response = a_Misc_encode_base64(user_password);\n      char *authorization =\n         dStrconcat(\"Authorization: Basic \", response, \"\\r\\n\", NULL);\n      dFree(realm->authorization);\n      realm->authorization = authorization;\n      dFree(response);\n      dStrshred(user_password);\n      dFree(user_password);\n   } else if (realm->type == DIGEST) {\n      dFree(realm->username);\n      realm->username = dStrdup(user);\n      realm->nonce_count = 0;\n      dFree(realm->nonce);\n      realm->nonce = dStrdup(data->auth_parse->nonce);\n      dFree(realm->opaque);\n      realm->opaque = dStrdup(data->auth_parse->opaque);\n      realm->algorithm = data->auth_parse->algorithm;\n      dFree(realm->domain);\n      realm->domain = dStrdup(data->auth_parse->domain);\n      realm->qop = data->auth_parse->qop;\n      dFree(realm->cnonce);\n      if (realm->qop != QOPNOTSET)\n         realm->cnonce = a_Digest_create_cnonce();\n      if (!a_Digest_compute_digest(realm, user, password)) {\n         MSG(\"Auth_do_auth_dialog_cb: a_Digest_compute_digest failed.\\n\");\n         dList_remove_fast(host->realms, realm);\n         Auth_realm_delete(realm);\n      }\n   } else {\n      MSG(\"Auth_do_auth_dialog_cb: Unknown auth type: %i\\n\",\n          realm->type);\n   }\n   dStrshred((char *)password);\n}\n\n/*\n * Return: Nonzero if we got new credentials from the user and everything\n * seems fine.\n */\nstatic int Auth_do_auth_dialog(const AuthParse_t *auth_parse,\n                               const DilloUrl *url)\n{\n   int ret;\n   char *title, *msg;\n   AuthDialogData_t *data;\n   const char *typestr = auth_parse->type == DIGEST ? \"Digest\" : \"Basic\";\n\n   _MSG(\"auth.c: Auth_do_auth_dialog: realm = '%s'\\n\", auth_parse->realm);\n\n   title = dStrconcat(\"Dillo: Password for \", auth_parse->realm, NULL);\n   msg = dStrconcat(\"The server at \", URL_HOST(url), \" requires a username\"\n                    \" and password for  \\\"\", auth_parse->realm, \"\\\".\\n\\n\"\n                    \"Authentication scheme: \", typestr, NULL);\n   data = dNew(AuthDialogData_t, 1);\n   data->auth_parse = auth_parse;\n   data->url = a_Url_dup(url);\n   ret = a_Dialog_user_password(title, msg, Auth_do_auth_dialog_cb, data);\n   dFree(title); dFree(msg);\n   a_Url_free((void *)data->url);\n   dFree(data);\n   return ret;\n}\n\n/*\n * Do authorization for an auth string.\n */\nstatic int Auth_do_auth(char *challenge, enum AuthParseHTTPAuthType_t type,\n                        const DilloUrl *url)\n{\n   AuthParse_t *auth_parse;\n   int reload = 0;\n\n   _MSG(\"auth.c: Auth_do_auth: challenge={%s}\\n\", challenge);\n   auth_parse = Auth_parse_new();\n   auth_parse->type = type;\n   Auth_parse_challenge(auth_parse, challenge);\n   if (auth_parse->ok)\n      reload =\n         Auth_do_auth_required(auth_parse, url) ?\n         Auth_do_auth_dialog(auth_parse, url)\n         : 1;\n   Auth_parse_free(auth_parse);\n\n   return reload;\n}\n\n/*\n * Given authentication challenge(s), prepare authorization.\n * Return: 0 on failure\n *         nonzero on success. A new query will be sent to the server.\n */\nint a_Auth_do_auth(Dlist *challenges, const DilloUrl *url)\n{\n   int i;\n   char *chal;\n\n   for (i = 0; (chal = dList_nth_data(challenges, i)); ++i)\n      if (!dStrnAsciiCasecmp(chal, \"Digest \", 7))\n         if (Auth_do_auth(chal, DIGEST, url))\n            return 1;\n   for (i = 0; (chal = dList_nth_data(challenges, i)); ++i)\n      if (!dStrnAsciiCasecmp(chal, \"Basic \", 6))\n         if (Auth_do_auth(chal, BASIC, url))\n            return 1;\n\n   return 0;\n}\n\n"
  },
  {
    "path": "src/auth.h",
    "content": "#ifndef __AUTH_H__\n#define __AUTH_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#include \"url.h\"\n\nenum AuthParseHTTPAuthType_t { TYPENOTSET, BASIC, DIGEST };\nenum AuthParseDigestAlgorithm_t { ALGORITHMNOTSET, MD5, MD5SESS };\nenum AuthParseDigestQOP_t { QOPNOTSET, AUTH, AUTHINT };\n\ntypedef struct {\n   enum AuthParseHTTPAuthType_t type;\n   char *name;\n   Dlist *paths; /* stripped of any trailing '/', so the root path is \"\" */\n   char *authorization; /* BASIC: the authorization request header */\n                        /* DIGEST: the hexdigest of A1             */\n   /* digest state ahead */\n   char *username;\n   char *cnonce;\n   unsigned int nonce_count;\n   char *nonce;\n   char *opaque;\n   enum AuthParseDigestAlgorithm_t algorithm;\n   char *domain; /* NOT USED */\n   enum AuthParseDigestQOP_t qop;\n} AuthRealm_t;\n\n\nchar *a_Auth_get_auth_str(const DilloUrl *url, const char *request_uri);\nint a_Auth_do_auth(Dlist *auth_string, const DilloUrl *url);\nvoid a_Auth_init(void);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* !__AUTH_H__ */\n"
  },
  {
    "path": "src/binaryconst.h",
    "content": "#ifndef __BINARYCONST_H__\n#define __BINARYCONST_H__\n\n/* binaryconst.h was integrated into the Dillo project in April 2004, and\n * presumably comes from the ancestor of the code found at\n * http://cprog.tomsweb.net/binconst.txt\n */\n\n/* Macros for allowing binary constants in C\n * By Tom Torfs - donated to the public domain */\n\n#define HEX__(n) 0x##n##LU\n\n/* 8-bit conversion function */\n#define B8__(x) ((x&0x0000000FLU)?1:0)  \\\n               +((x&0x000000F0LU)?2:0)  \\\n               +((x&0x00000F00LU)?4:0)  \\\n               +((x&0x0000F000LU)?8:0)  \\\n               +((x&0x000F0000LU)?16:0) \\\n               +((x&0x00F00000LU)?32:0) \\\n               +((x&0x0F000000LU)?64:0) \\\n               +((x&0xF0000000LU)?128:0)\n\n\n/*\n * *** USER MACROS ***\n */\n\n/* for upto 8-bit binary constants */\n#define B8(d) ((unsigned char)B8__(HEX__(d)))\n\n/* for upto 16-bit binary constants, MSB first */\n#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb))\n\n/*\n * Sample usage:\n *    B8(01010101) = 85\n *    B16(10101010,01010101) = 43605\n */\n\n\n#endif /* __BINARYCONST_H__ */\n\n"
  },
  {
    "path": "src/bitvec.c",
    "content": "/*\n * File: bitvec.c\n *\n * Copyright 2001 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * A simple ADT for bit arrays\n */\n\n#include \"../dlib/dlib.h\"\n#include \"bitvec.h\"\n\n\n/*\n * Create a new bitvec with 'num_bits' size\n */\nbitvec_t *a_Bitvec_new(int num_bits)\n{\n   bitvec_t *bvec = dNew(bitvec_t, 1);\n\n   bvec->vec = dNew0(uchar_t, num_bits/BVEC_SIZE + 1);\n   bvec->len = num_bits;\n   return bvec;\n}\n\n/*\n * Clear a bitvec\n */\nvoid a_Bitvec_clear(bitvec_t *bvec)\n{\n   memset(bvec->vec, 0, sizeof(uchar_t) * bvec->len/BVEC_SIZE + 1);\n}\n\n/*\n * Free a bitvec\n */\nvoid a_Bitvec_free(bitvec_t *bvec)\n{\n   if (bvec) {\n      dFree(bvec->vec);\n      dFree(bvec);\n   }\n}\n\n/*\n * Get a bit\n */\nint a_Bitvec_get_bit(bitvec_t *bvec, int pos)\n{\n   dReturn_val_if_fail (pos < bvec->len, 0);\n   return (bvec->vec[pos/BVEC_SIZE] & 1 << pos % BVEC_SIZE);\n}\n\n/*\n * Set a bit\n */\nvoid a_Bitvec_set_bit(bitvec_t *bvec, int pos)\n{\n   dReturn_if_fail (pos < bvec->len);\n   bvec->vec[pos/BVEC_SIZE] |= 1 << (pos % BVEC_SIZE);\n}\n"
  },
  {
    "path": "src/bitvec.h",
    "content": "#ifndef __BITVEC_H__\n#define __BITVEC_H__\n\n#include \"d_size.h\"\n\n#define BVEC_TYPE uchar_t\n#define BVEC_SIZE sizeof(BVEC_TYPE)\n\ntypedef struct {\n   BVEC_TYPE *vec;\n   int len;       /* number of bits [1 based] */\n} bitvec_t;\n\n\n/*\n * Function prototypes\n */\nbitvec_t *a_Bitvec_new(int bits);\nvoid a_Bitvec_free(bitvec_t *bvec);\nint a_Bitvec_get_bit(bitvec_t *bvec, int pos);\nvoid a_Bitvec_set_bit(bitvec_t *bvec, int pos);\nvoid a_Bitvec_clear(bitvec_t *bvec);\n\n/*\n#define a_Bitvec_get_bit(bvec,pos) \\\n   ((bvec)->vec[(pos)/BVEC_SIZE] & 1 << (pos) % BVEC_SIZE)\n\n#define a_Bitvec_set_bit(bvec,pos) \\\n   ((bvec)->vec[(pos)/BVEC_SIZE] |= 1 << (pos) % BVEC_SIZE)\n*/\n#define a_Bitvec_clear_bit(bvec,pos) \\\n   ((bvec)->vec[(pos)/BVEC_SIZE] &= ~(1 << (pos) % BVEC_SIZE))\n\n\n#endif /* __BITVEC_H__ */\n"
  },
  {
    "path": "src/bm.txt",
    "content": ":s0: Searching\n:s1: General\n:s2: News\n:s3: Videos\n:s4: Hacking and Tech\n:s5: Local Services\n:s6: Blogs\n:s7: Forums and Lists\n:s8: Popular Sites\n:s9: Social\n:s10: Other\n:s11: Magazines\n\ns0 https://wiby.me Wiby - Search user submitted light/personable websites\ns0 https://duckduckgo.com/lite Duck Duck Go\ns0 https://google.com Google\ns0 https://wikipedia.org Wikipedia\ns0 https://www.refdesk.com/ Reference Desk\ns0 https://archive.org/?noscript=true Archive.org light\ns0 https://unsplash.com Unsplash - image search\ns1 https://wttr.in Weather\ns1 https://www.gutenberg.org/ Gutenberg free books\ns2 https://68k.news 68k\ns2 https://skimfeed.com SkimFeed\ns2 https://tildes.net Tildes\ns2 https://legiblenews.com Legible news\ns2 https://news.yahoo.com Yahoo News\ns2 https://teddit.net Teddit - Reddit alternative\ns2 https://www.reddit.com/.compact Reddit Compact\ns2 https://old.reddit.com/ Reddit Old\ns2 https://lite.cnn.com/en CNN Lite\ns2 https://text.npr.org/ NPR (Text)\ns2 https://www.reuters.com/commentary Reuters\ns2 https://www.newshound.co/editions/en-us/ News Hound\ns2 https://www.bbc.co.uk/ BBC\ns2 https://nytimes.com  NY Times\ns3 https://localhost/tools/youtube/ Youtube local\ns3 https://invidious.kavin.rocks/feed/popular Youtube kavin\ns3 https://redirect.invidious.io Youtube invidious\ns3 https://open-video.org/ Open Video Project\ns3 https://www.ted.com/talks TED talks\ns3 https;//archive.org/details/movies The Archive movies\ns3 https://teddit.net/r/MadeMeSmile/comments/qq16z0/this_duck_ran_the_new_york_marathon_and_look_at/ Test video page\ns4 https://alterslash.org/ Alterdash\ns4 https://lobste.rs/ Lobsters Techie news\ns4 https://lwn.net/ Linux Weekly News\ns4 https://retro.hackaday.com Hackaday\ns4 https://news.ycombinator.com/news Ycombinator Hacking news\ns4 https://anonymousplanet.org/guide.html Hitchiker's Guide to Online Anonymity\ns4 https://liliputing.com/ Lilliputing\ns4 https://www.i18nguy.com/humor/support.html  Customer Support Stories\ns5 https://localhost Local webserver\ns5 https://localhost:8000 Wiki (if turned on)\ns5 https://127.0.0.1/blog/ Blog\ns6 https://www.paritybit.ca/ Parity Bit's blog\ns6 https://www.chriszacharias.com Chris Zacharia's blog\ns6 https://greycoder.com/interesting-new-apps/ Greycoder\ns6 https://www.crazythoughts.com/ Crazy Thoughts\ns6 https://txti.es Txties - Microblog without an account\ns6 https://smol.pub/feed Smol Microblog recent posts\ns6 https://bearblog.dev/discover/?newest=true Bear Microblog recent posts\ns6 https://txti.es/hyperlight-webpages  - HyperLight Txti page\ns6 https://txti.es/online-etiquette - Online Etiquette\ns7 https://inkscape.org/forums Inkscape\ns7 https://forum.puppylinux.com/ Puppy Linux\ns7 https://groups.google.com/g/yad-common Yad developr Mailing List\ns7 https://forums.raspberrypi.com Raspberry Pi Forum\ns8 https://www.amazon.com Amazon\ns8 https://ebay.com Ebay\ns8 https://craigslist.com Craigslist\ns8 https://www.allrecipes.com/ AllRecipes\ns8 https://www.yelp.com Yelp\ns8 https://webmd.com WebMD\ns9 https://facebook.com Facebook\ns9 https://nitter.net/search?f=users&q=news Nitter (anonymous Twitter)\ns9 https://mastodon.social/@anonypla Mastodon\ns9 https://anonymous-story.com Instagram alternative\ns10 https://www.webdesignerdepot.com WebDesigner Depot\ns10 https://dir.xiph.org Xiph music streams directory\ns11 https://popularmechanics.com Popular Mechanics\ns11 https://popularscience.com Popular Science\ns11 https://www.lowtechmagazine.com Low Tech Magazine\ns11 https://farmersalmanac.com Farmer's Almanac\ns11 https://motherearthnews.com Mother Earth News\n"
  },
  {
    "path": "src/bookmark.c",
    "content": "/*\n * File: bookmark.c\n *\n * Copyright 2002-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <stdlib.h>\n\n#include \"msg.h\"\n#include \"history.h\"\n#include \"capi.h\"\n#include \"bookmark.h\"  /* for prototypes */\n#include \"../dpip/dpip.h\"\n\n\n\n/*\n * Have a short chat with the bookmarks server,\n * and finally ask it to add a new bookmark.\n * (this is an example of dpi chat)\n */\nvoid a_Bookmarks_chat_add(BrowserWindow *Bw, char *Cmd, char *answer)\n{\n   static char *cmd1 = NULL, *cmd2 = NULL, *cmd3 = NULL, *cmd4 = NULL;\n   static BrowserWindow *bw = NULL;\n\n   if (!cmd1) {\n      cmd1 = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"chat\", \"Hi server\");\n      cmd2 = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"chat\",\n                              \"I want to set a bookmark\");\n      cmd3 = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"chat\", \"Sure it is!\");\n   }\n\n   _MSG(\"a_Bookmarks_chat_add\\n answer=%s\\n\", answer ? answer : \"(null)\");\n\n   if (Bw)\n      bw = Bw;\n   if (!cmd4 && Cmd)\n      cmd4 = dStrdup(Cmd);\n\n   if (!answer) {\n      a_Capi_dpi_send_cmd(NULL, bw, cmd1, \"bookmarks\", 1);\n\n   } else {\n      /* we have an answer */\n      if (answer) {\n         if (*answer == 'H') {\n            /* \"Hi browser\" */\n            a_Capi_dpi_send_cmd(NULL, bw, cmd2, \"bookmarks\", 0);\n         } else if (*answer == 'I') {\n            /* \"Is it worth?\" */\n            a_Capi_dpi_send_cmd(NULL, bw, cmd3, \"bookmarks\", 0);\n         } else if (*answer == 'O') {\n            /* \"OK, send it!\" */\n            a_Capi_dpi_send_cmd(NULL, bw, cmd4, \"bookmarks\", 0);\n            dFree(cmd4);\n            cmd4 = NULL;\n         }\n      }\n   }\n}\n\n/*\n * Add the new bookmark through the bookmarks server\n */\nvoid a_Bookmarks_add(BrowserWindow *bw, const DilloUrl *url)\n{\n   const char *title;\n   char *cmd;\n\n   dReturn_if_fail(url != NULL);\n\n   /* if the page has no title, we'll use the url string */\n   title = a_History_get_title_by_url(url, 1);\n\n   cmd = a_Dpip_build_cmd(\"cmd=%s url=%s title=%s\",\n                          \"add_bookmark\", URL_STR(url), title);\n   a_Bookmarks_chat_add(bw, cmd, NULL);\n   dFree(cmd);\n}\n\n"
  },
  {
    "path": "src/bookmark.h",
    "content": "#ifndef __BOOKMARK_H__\n#define __BOOKMARK_H__\n\n#include \"bw.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nvoid a_Bookmarks_add(BrowserWindow *bw, const DilloUrl *url);\n\n/* TODO: this is for testing purposes */\nvoid a_Bookmarks_chat_add(BrowserWindow *Bw, char *Cmd, char *answer);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __BOOKMARK_H__ */\n"
  },
  {
    "path": "src/bw.c",
    "content": "/*\n * File: bw.c\n *\n * Copyright (C) 2006-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/* Data structures for each browser window */\n\n\n#include \"bw.h\"\n#include \"msg.h\"\n#include \"list.h\"\n#include \"capi.h\"\n#include \"uicmd.hh\"\n\n\n/*\n * Local Data\n */\n/* A list of working browser windows */\nstatic BrowserWindow **bws;\nstatic int num_bws, num_bws_max;\n\n\n/*\n * Initialize global data\n */\nvoid a_Bw_init(void)\n{\n   num_bws = 0;\n   num_bws_max = 16;\n   bws = NULL;\n}\n\n/*\n * Create a new browser window and return it.\n * (the new window is stored in browser_window[])\n */\nBrowserWindow *a_Bw_new()\n{\n   BrowserWindow *bw;\n\n   /* We use dNew0() to zero the memory */\n   bw = dNew0(BrowserWindow, 1);\n   a_List_add(bws, num_bws, num_bws_max);\n   bws[num_bws++] = bw;\n\n   /* Initialize nav_stack */\n   bw->nav_stack = dList_new(8);\n   bw->nav_stack_ptr = -1;\n\n   /* Init expect */\n   bw->nav_expect_url = NULL;\n\n   bw->redirect_level = 0;\n   bw->meta_refresh_status = 0;\n   bw->meta_refresh_url = NULL;\n\n   bw->RootClients = dList_new(8);\n   bw->ImageClients = dList_new(8);\n   bw->NumImages = 0;\n   bw->NumImagesGot = 0;\n   bw->NumPendingStyleSheets = 0;\n   bw->PageUrls = dList_new(8);\n   bw->Docs = dList_new(8);\n\n   bw->num_page_bugs = 0;\n   bw->page_bugs = dStr_new(\"\");\n\n   /* now that the bw is made, let's customize it.. */\n   //Interface_browser_window_customize(bw);\n\n   return bw;\n}\n\n/*\n * Free resources associated to a bw.\n */\nvoid a_Bw_free(BrowserWindow *bw)\n{\n   int i, j;\n\n   for (i = 0; i < num_bws; i++) {\n      if (bws[i] == bw) {\n         a_List_remove(bws, i, num_bws);\n\n         dList_free(bw->RootClients);\n         dList_free(bw->ImageClients);\n         dList_free(bw->Docs);\n\n         a_Url_free(bw->nav_expect_url);\n         for (j = 0; j < dList_length(bw->PageUrls); ++j)\n            a_Url_free(dList_nth_data(bw->PageUrls, j));\n         dList_free(bw->PageUrls);\n\n         for (j = 0; j < dList_length(bw->nav_stack); ++j)\n            dFree(dList_nth_data(bw->nav_stack, j));\n         dList_free(bw->nav_stack);\n\n         a_Url_free(bw->meta_refresh_url);\n\n         dStr_free(bw->page_bugs, 1);\n         dFree(bw);\n         break;\n      }\n   }\n}\n\n/*- Clients ----------------------------------------------------------------*/\n/*\n * Add a reference to a cache-client. It is kept int this bw's list.\n * This helps us keep track of which are active in the window so that it's\n * possible to abort/stop them.\n * (Root: Flag, whether a Root URL or not)\n *\n * TODO: Make NumImages count different images.\n */\nvoid a_Bw_add_client(BrowserWindow *bw, int Key, int Root)\n{\n   dReturn_if_fail ( bw != NULL );\n\n   if (Root) {\n      dList_append(bw->RootClients, INT2VOIDP(Key));\n   } else {\n      dList_append(bw->ImageClients, INT2VOIDP(Key));\n      bw->NumImages++;\n      /* --Images progress-bar stuff-- */\n      a_UIcmd_set_img_prog(bw, bw->NumImagesGot, bw->NumImages, 1);\n   }\n   if (dList_length(bw->RootClients) + dList_length(bw->ImageClients) == 1)\n      a_UIcmd_set_buttons_sens(bw);\n}\n\n/*\n * Remove the cache-client from the bw's list\n * (client can be a image or a html page)\n * Return: 0 if found, 1 otherwise.\n */\nint a_Bw_remove_client(BrowserWindow *bw, int ClientKey)\n{\n   void *data;\n\n   if ((data = dList_find(bw->RootClients, INT2VOIDP(ClientKey)))) {\n      dList_remove_fast(bw->RootClients, data);\n   } else if ((data = dList_find(bw->ImageClients, INT2VOIDP(ClientKey)))) {\n      dList_remove_fast(bw->ImageClients, data);\n      ++bw->NumImagesGot;\n   }\n   return data ? 0 : 1;\n}\n\n/*\n * Close a cache-client upon successful retrieval.\n * Remove the cache-client from the bw list and update the meters.\n * (client can be a image or a html page)\n */\nvoid a_Bw_close_client(BrowserWindow *bw, int ClientKey)\n{\n   if (a_Bw_remove_client(bw, ClientKey) == 0) {\n      a_UIcmd_set_img_prog(bw, bw->NumImagesGot, bw->NumImages, 1);\n      if (bw->NumImagesGot == bw->NumImages)\n         a_UIcmd_set_img_prog(bw, 0, 0, 0);\n      if (dList_length(bw->RootClients) == 0)\n         a_UIcmd_set_buttons_sens(bw);\n   }\n}\n\n/*\n * Stop the active clients of this bw's top page.\n * Note: rendering stops, but the cache continues to be fed.\n */\nvoid a_Bw_stop_clients(BrowserWindow *bw, int flags)\n{\n   void *data;\n\n   if (flags & BW_Root) {\n      /* Remove root clients */\n      while ((data = dList_nth_data(bw->RootClients, 0))) {\n         a_Capi_stop_client(VOIDP2INT(data), (flags & BW_Force));\n         dList_remove_fast(bw->RootClients, data);\n      }\n   }\n\n   if (flags & BW_Img) {\n      /* Remove image clients */\n      while ((data = dList_nth_data(bw->ImageClients, 0))) {\n         a_Capi_stop_client(VOIDP2INT(data), (flags & BW_Force));\n         dList_remove_fast(bw->ImageClients, data);\n      }\n   }\n}\n\n/*- Page -------------------------------------------------------------------*/\n/*\n * Add an URL to the browser window's list.\n * This helps us keep track of page-requested URLs so that it's\n * possible to stop, abort and reload them.\n */\nvoid a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url)\n{\n   dReturn_if_fail ( bw != NULL && Url != NULL );\n\n   if (!dList_find_custom(bw->PageUrls, Url, (dCompareFunc)a_Url_cmp)) {\n      dList_append(bw->PageUrls, a_Url_dup(Url));\n   }\n}\n\n/*\n * Add a document to the browser window's list.\n */\nvoid a_Bw_add_doc(BrowserWindow *bw, void *vdoc)\n{\n   dReturn_if_fail ( bw != NULL && vdoc != NULL);\n\n   dList_append(bw->Docs, vdoc);\n}\n\n/*\n * Get current document.\n */\nvoid *a_Bw_get_current_doc(BrowserWindow *bw)\n{\n   void *doc = NULL;\n   int len = dList_length(bw->Docs);\n\n   if (len == 1)\n      doc = dList_nth_data(bw->Docs, 0);\n   else if (len > 1)\n      MSG(\"a_Bw_get_current_doc() multiple docs not implemented\\n\");\n\n   return doc;\n}\n\n/*\n * Get document by URL.\n *\n * This is currently used by popup menus that need to ensure that the\n * page has not changed while the menu was popped up.\n */\nvoid *a_Bw_get_url_doc(BrowserWindow *bw, const DilloUrl *url)\n{\n   void *doc = NULL;\n\n   if (url && dList_find_custom(bw->PageUrls, url, (dCompareFunc)a_Url_cmp)) {\n      doc = a_Bw_get_current_doc(bw);\n   }\n   return doc;\n}\n\n/*\n * Remove a document from the bw's list\n */\nvoid a_Bw_remove_doc(BrowserWindow *bw, void *vdoc)\n{\n   void *data;\n\n   if ((data = dList_find(bw->Docs, vdoc))) {\n      dList_remove_fast(bw->Docs, data);\n   }\n}\n\n/*- Cleanup ----------------------------------------------------------------*/\n/*\n * Empty RootClients, ImageClients and PageUrls lists and\n * reset progress bar data.\n */\nvoid a_Bw_cleanup(BrowserWindow *bw)\n{\n   void *data;\n\n   /* Remove root clients */\n   while ((data = dList_nth_data(bw->RootClients, 0))) {\n      dList_remove_fast(bw->RootClients, data);\n   }\n   /* Remove image clients */\n   while ((data = dList_nth_data(bw->ImageClients, 0))) {\n      dList_remove_fast(bw->ImageClients, data);\n   }\n   /* Remove PageUrls */\n   while ((data = dList_nth_data(bw->PageUrls, 0))) {\n      a_Url_free(data);\n      dList_remove_fast(bw->PageUrls, data);\n   }\n\n   /* Zero image-progress data */\n   bw->NumImages = 0;\n   bw->NumImagesGot = 0;\n\n   /* Zero stylesheet counter */\n   bw->NumPendingStyleSheets = 0;\n}\n\n/*--------------------------------------------------------------------------*/\n\nint a_Bw_num()\n{\n   return num_bws;\n}\n\n/*\n * Return a bw by index\n */\nBrowserWindow *a_Bw_get(int i)\n{\n   if (i >= 0 && i < num_bws)\n      return bws[i];\n   return NULL;\n}\n\n/* expect API ------------------------------------------------------------- */\n\nvoid a_Bw_expect(BrowserWindow *bw, const DilloUrl *url)\n{\n   a_Url_free(bw->nav_expect_url);\n   bw->nav_expect_url = a_Url_dup(url);\n}\n\nvoid a_Bw_cancel_expect(BrowserWindow *bw)\n{\n   a_Url_free(bw->nav_expect_url);\n   bw->nav_expect_url = NULL;\n}\n\nbool_t a_Bw_expecting(BrowserWindow *bw)\n{\n   return (bw->nav_expect_url != NULL);\n}\n\nconst DilloUrl *a_Bw_expected_url(BrowserWindow *bw)\n{\n   return bw->nav_expect_url;\n}\n\n"
  },
  {
    "path": "src/bw.h",
    "content": "#ifndef __BW_H__\n#define __BW_H__\n\n#include \"url.h\"     /* for DilloUrl */\n\n/*\n * Flag Defines for a_Bw_stop_clients()\n */\n#define BW_Root            (1)  /* Root URLs */\n#define BW_Img             (2)  /* Image URLs */\n#define BW_Force           (4)  /* Stop connection too */\n\n\n/* browser_window contains the specific data for a single window */\ntypedef struct {\n   /* Pointer to the UI object this bw belongs to */\n   void *ui;\n\n   /* All the rendering is done by this.\n    * It is defined as a void pointer to avoid C++ in this structure.\n    * C++ sources have to include \"dw/core.hh\" and cast it into an object. */\n   void *render_layout;\n\n   /* Root document(s). Currently only used by DilloHtml */\n   Dlist *Docs;\n\n   /* A list of active cache clients in the window (The primary Key) */\n   Dlist *RootClients;\n   /* Image Keys for all active connections in the window */\n   Dlist *ImageClients;\n   /* Number of images in the page */\n   int NumImages;\n   /* Number of images already loaded */\n   int NumImagesGot;\n   /* Number of not yet arrived style sheets */\n   int NumPendingStyleSheets;\n   /* List of all Urls requested by this page (and its types) */\n   Dlist *PageUrls;\n\n   /* The navigation stack (holds indexes to history list) */\n   Dlist *nav_stack;\n   /* 'nav_stack_ptr' refers to what's being displayed */\n   int nav_stack_ptr;        /* [0 based; -1 = empty] */\n   /* When the user clicks a link, the URL isn't pushed directly to history;\n    * nav_expect_url holds it until a dw is assigned to it. Only then an entry\n    * is made in history and referenced at the top of nav_stack */\n   DilloUrl *nav_expect_url;\n\n   /* Counter for the number of hops on a redirection. Used to stop\n    * redirection loops (accounts for WEB_RootUrl only) */\n   int redirect_level;\n\n   /* Url for zero-delay redirections in the META element */\n   int meta_refresh_status;\n   DilloUrl *meta_refresh_url;\n\n   /* HTML-bugs detected at parse time */\n   int num_page_bugs;\n   Dstr *page_bugs;\n} BrowserWindow;\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\nvoid a_Bw_init(void);\nBrowserWindow *a_Bw_new();\nvoid a_Bw_free(BrowserWindow *bw);\nBrowserWindow *a_Bw_get(int i);\nint a_Bw_num();\n\nvoid a_Bw_add_client(BrowserWindow *bw, int Key, int Root);\nint a_Bw_remove_client(BrowserWindow *bw, int ClientKey);\nvoid a_Bw_close_client(BrowserWindow *bw, int ClientKey);\nvoid a_Bw_stop_clients(BrowserWindow *bw, int flags);\nvoid a_Bw_add_doc(BrowserWindow *bw, void *vdoc);\nvoid *a_Bw_get_current_doc(BrowserWindow *bw);\nvoid *a_Bw_get_url_doc(BrowserWindow *bw, const DilloUrl *Url);\nvoid a_Bw_remove_doc(BrowserWindow *bw, void *vdoc);\nvoid a_Bw_add_url(BrowserWindow *bw, const DilloUrl *Url);\nvoid a_Bw_cleanup(BrowserWindow *bw);\n/* expect API */\nvoid a_Bw_expect(BrowserWindow *bw, const DilloUrl *Url);\nvoid a_Bw_cancel_expect(BrowserWindow *bw);\nbool_t a_Bw_expecting(BrowserWindow *bw);\nconst DilloUrl *a_Bw_expected_url(BrowserWindow *bw);\n\ntypedef void (*BwCallback_t)(BrowserWindow *bw, const void *data);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __BROWSER_H__ */\n\n"
  },
  {
    "path": "src/cache.c",
    "content": "/*\n * File: cache.c\n *\n * Copyright 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Dillo's cache module\n */\n\n#include <sys/types.h>\n\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"msg.h\"\n#include \"IO/Url.h\"\n#include \"IO/IO.h\"\n#include \"web.hh\"\n#include \"dicache.h\"\n#include \"nav.h\"\n#include \"cookies.h\"\n#include \"hsts.h\"\n#include \"misc.h\"\n#include \"capi.h\"\n#include \"decode.h\"\n#include \"auth.h\"\n#include \"domain.h\"\n#include \"timeout.hh\"\n#include \"uicmd.hh\"\n\n/* Maximum initial size for the automatically-growing data buffer */\n#define MAX_INIT_BUF  1024*1024\n/* Maximum filesize for a URL, before offering a download */\n#define HUGE_FILESIZE 15*1024*1024\n\n/*\n *  Local data types\n */\n\ntypedef struct {\n   const DilloUrl *Url;      /* Cached Url. Url is used as a primary Key */\n   char *TypeDet;            /* MIME type string (detected from data) */\n   char *TypeHdr;            /* MIME type string as from the HTTP Header */\n   char *TypeMeta;           /* MIME type string from META HTTP-EQUIV */\n   char *TypeNorm;           /* MIME type string normalized */\n   Dstr *Header;             /* HTTP header */\n   const DilloUrl *Location; /* New URI for redirects */\n   Dlist *Auth;              /* Authentication fields */\n   Dstr *Data;               /* Pointer to raw data */\n   Dstr *UTF8Data;           /* Data after charset translation */\n   int DataRefcount;         /* Reference count */\n   DecodeTransfer *TransferDecoder;  /* Transfer decoder (e.g., chunked) */\n   Decode *ContentDecoder;   /* Data decoder (e.g., gzip) */\n   Decode *CharsetDecoder;   /* Translates text to UTF-8 encoding */\n   int ExpectedSize;         /* Goal size of the HTTP transfer (0 if unknown)*/\n   int TransferSize;         /* Actual length of the HTTP transfer */\n   uint_t Flags;             /* See Flag Defines in cache.h */\n} CacheEntry_t;\n\n\n/*\n *  Local data\n */\n/* A sorted list for cached data. Holds pointers to CacheEntry_t structs */\nstatic Dlist *CachedURLs;\n\n/* A list for cache clients.\n * Although implemented as a list, we'll call it ClientQueue  --Jcid */\nstatic Dlist *ClientQueue;\n\n/* A list for delayed clients (it holds weak pointers to cache entries,\n * which are used to make deferred calls to Cache_process_queue) */\nstatic Dlist *DelayedQueue;\nstatic uint_t DelayedQueueIdleId = 0;\n\n\n/*\n *  Forward declarations\n */\nstatic CacheEntry_t *Cache_process_queue(CacheEntry_t *entry);\nstatic void Cache_delayed_process_queue(CacheEntry_t *entry);\nstatic void Cache_auth_entry(CacheEntry_t *entry, BrowserWindow *bw);\nstatic void Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds);\n\n/*\n * Determine if two cache entries are equal (used by CachedURLs)\n */\nstatic int Cache_entry_cmp(const void *v1, const void *v2)\n{\n   const CacheEntry_t *d1 = v1, *d2 = v2;\n\n   return a_Url_cmp(d1->Url, d2->Url);\n}\n\n/*\n * Determine if two cache entries are equal, using a URL as key.\n */\nstatic int Cache_entry_by_url_cmp(const void *v1, const void *v2)\n{\n   const DilloUrl *u1 = ((CacheEntry_t*)v1)->Url;\n   const DilloUrl *u2 = v2;\n\n   return a_Url_cmp(u1, u2);\n}\n\n/*\n * Initialize cache data\n */\nvoid a_Cache_init(void)\n{\n   ClientQueue = dList_new(32);\n   DelayedQueue = dList_new(32);\n   CachedURLs = dList_new(256);\n\n   /* inject the splash screen in the cache */\n   {\n      DilloUrl *url = a_Url_new(\"about:splash\", NULL);\n      Dstr *ds = dStr_new(AboutSplash);\n      Cache_entry_inject(url, ds);\n      dStr_free(ds, 1);\n      a_Url_free(url);\n   }\n}\n\n/* Client operations ------------------------------------------------------ */\n\n/*\n * Add a client to ClientQueue.\n *  - Every client-field is just a reference (except 'Web').\n *  - Return a unique number for identifying the client.\n */\nstatic int Cache_client_enqueue(const DilloUrl *Url, DilloWeb *Web,\n                                 CA_Callback_t Callback, void *CbData)\n{\n   static int ClientKey = 0; /* Provide a primary key for each client */\n   CacheClient_t *NewClient;\n\n   if (ClientKey < INT_MAX) /* check for integer overflow */\n      ClientKey++;\n   else\n      ClientKey = 1;\n\n   NewClient = dNew(CacheClient_t, 1);\n   NewClient->Key = ClientKey;\n   NewClient->Url = Url;\n   NewClient->Version = 0;\n   NewClient->Buf = NULL;\n   NewClient->BufSize = 0;\n   NewClient->Callback = Callback;\n   NewClient->CbData = CbData;\n   NewClient->Web    = Web;\n\n   dList_append(ClientQueue, NewClient);\n\n   return ClientKey;\n}\n\n/*\n * Compare function for searching a Client by its key\n */\nstatic int Cache_client_by_key_cmp(const void *client, const void *key)\n{\n   return ((CacheClient_t *)client)->Key - VOIDP2INT(key);\n}\n\n/*\n * Remove a client from the queue\n */\nstatic void Cache_client_dequeue(CacheClient_t *Client)\n{\n   if (Client) {\n      dList_remove(ClientQueue, Client);\n      a_Web_free(Client->Web);\n      dFree(Client);\n   }\n}\n\n\n/* Entry operations ------------------------------------------------------- */\n\n/*\n * Set safe values for a new cache entry\n */\nstatic void Cache_entry_init(CacheEntry_t *NewEntry, const DilloUrl *Url)\n{\n   NewEntry->Url = a_Url_dup(Url);\n   NewEntry->TypeDet = NULL;\n   NewEntry->TypeHdr = NULL;\n   NewEntry->TypeMeta = NULL;\n   NewEntry->TypeNorm = NULL;\n   NewEntry->Header = dStr_new(\"\");\n   NewEntry->Location = NULL;\n   NewEntry->Auth = NULL;\n   NewEntry->Data = dStr_sized_new(8*1024);\n   NewEntry->UTF8Data = NULL;\n   NewEntry->DataRefcount = 0;\n   NewEntry->TransferDecoder = NULL;\n   NewEntry->ContentDecoder = NULL;\n   NewEntry->CharsetDecoder = NULL;\n   NewEntry->ExpectedSize = 0;\n   NewEntry->TransferSize = 0;\n   NewEntry->Flags = CA_IsEmpty | CA_InProgress | CA_KeepAlive;\n}\n\n/*\n * Get the data structure for a cached URL (using 'Url' as the search key)\n * If 'Url' isn't cached, return NULL\n */\nstatic CacheEntry_t *Cache_entry_search(const DilloUrl *Url)\n{\n   return dList_find_sorted(CachedURLs, Url, Cache_entry_by_url_cmp);\n}\n\n/*\n * Given a URL, find its cache entry, following redirections.\n */\nstatic CacheEntry_t *Cache_entry_search_with_redirect(const DilloUrl *Url)\n{\n   int i;\n   CacheEntry_t *entry;\n\n   for (i = 0; (entry = Cache_entry_search(Url)); ++i) {\n\n      /* Test for a redirection loop */\n      if (entry->Flags & CA_RedirectLoop || i == 3) {\n         _MSG_WARN(\"Redirect loop for URL: >%s<\\n\", URL_STR_(Url));\n         break;\n      }\n      /* Test for a working redirection */\n      if (entry->Flags & CA_Redirect && entry->Location) {\n         Url = entry->Location;\n      } else\n         break;\n   }\n   return entry;\n}\n\n/*\n * Allocate and set a new entry in the cache list\n */\nstatic CacheEntry_t *Cache_entry_add(const DilloUrl *Url)\n{\n   CacheEntry_t *old_entry, *new_entry;\n\n   if ((old_entry = Cache_entry_search(Url))) {\n      MSG_WARN(\"Cache_entry_add, leaking an entry.\\n\");\n      dList_remove(CachedURLs, old_entry);\n   }\n\n   new_entry = dNew(CacheEntry_t, 1);\n   Cache_entry_init(new_entry, Url);  /* Set safe values */\n   dList_insert_sorted(CachedURLs, new_entry, Cache_entry_cmp);\n   return new_entry;\n}\n\n/*\n * Inject full page content directly into the cache.\n * Used for \"about:splash\". May be used for \"about:cache\" too.\n */\nstatic void Cache_entry_inject(const DilloUrl *Url, Dstr *data_ds)\n{\n   CacheEntry_t *entry;\n\n   if (!(entry = Cache_entry_search(Url)))\n      entry = Cache_entry_add(Url);\n   entry->Flags = CA_GotHeader + CA_GotLength + CA_InternalUrl;\n   if (data_ds->len)\n      entry->Flags &= ~CA_IsEmpty;\n   dStr_truncate(entry->Data, 0);\n   dStr_append_l(entry->Data, data_ds->str, data_ds->len);\n   dStr_fit(entry->Data);\n   entry->ExpectedSize = entry->TransferSize = entry->Data->len;\n}\n\n/*\n *  Free Authentication fields.\n */\nstatic void Cache_auth_free(Dlist *auth)\n{\n   int i;\n   void *auth_field;\n   for (i = 0; (auth_field = dList_nth_data(auth, i)); ++i)\n      dFree(auth_field);\n   dList_free(auth);\n}\n\n/*\n *  Free the components of a CacheEntry_t struct.\n */\nstatic void Cache_entry_free(CacheEntry_t *entry)\n{\n   a_Url_free((DilloUrl *)entry->Url);\n   dFree(entry->TypeDet);\n   dFree(entry->TypeHdr);\n   dFree(entry->TypeMeta);\n   dFree(entry->TypeNorm);\n   dStr_free(entry->Header, TRUE);\n   a_Url_free((DilloUrl *)entry->Location);\n   Cache_auth_free(entry->Auth);\n   dStr_free(entry->Data, 1);\n   dStr_free(entry->UTF8Data, 1);\n   if (entry->CharsetDecoder)\n      a_Decode_free(entry->CharsetDecoder);\n   if (entry->TransferDecoder)\n      a_Decode_transfer_free(entry->TransferDecoder);\n   if (entry->ContentDecoder)\n      a_Decode_free(entry->ContentDecoder);\n   dFree(entry);\n}\n\n/*\n * Remove an entry, from the cache.\n * All the entry clients are removed too! (it may stop rendering of this\n * same resource on other windows, but nothing more).\n */\nstatic void Cache_entry_remove(CacheEntry_t *entry, DilloUrl *url)\n{\n   int i;\n   CacheClient_t *Client;\n\n   if (!entry && !(entry = Cache_entry_search(url)))\n      return;\n   if (entry->Flags & CA_InternalUrl)\n      return;\n\n   /* remove all clients for this entry */\n   for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) {\n      if (Client->Url == entry->Url) {\n         a_Cache_stop_client(Client->Key);\n         --i;\n      }\n   }\n\n   /* remove from DelayedQueue */\n   dList_remove(DelayedQueue, entry);\n\n   /* remove from dicache */\n   a_Dicache_invalidate_entry(entry->Url);\n\n   /* remove from cache */\n   dList_remove(CachedURLs, entry);\n   Cache_entry_free(entry);\n}\n\n/*\n * Wrapper for capi.\n */\nvoid a_Cache_entry_remove_by_url(DilloUrl *url)\n{\n   Cache_entry_remove(NULL, url);\n}\n\n/* Misc. operations ------------------------------------------------------- */\n\n/*\n * Try finding the url in the cache. If it hits, send the cache contents\n * from there. If it misses, set up a new connection.\n *\n * - 'Web' is an auxiliary data structure with misc. parameters.\n * - 'Call' is the callback that receives the data\n * - 'CbData' is custom data passed to 'Call'\n *   Note: 'Call' and/or 'CbData' can be NULL, in that case they get set\n *   later by a_Web_dispatch_by_type, based on content/type and 'Web' data.\n *\n * Return value: A primary key for identifying the client,\n */\nint a_Cache_open_url(void *web, CA_Callback_t Call, void *CbData)\n{\n   int ClientKey;\n   CacheEntry_t *entry;\n   DilloWeb *Web = web;\n   DilloUrl *Url = Web->url;\n\n   if (URL_FLAGS(Url) & URL_E2EQuery) {\n      /* remove current entry */\n      Cache_entry_remove(NULL, Url);\n   }\n\n   if ((entry = Cache_entry_search(Url))) {\n      /* URL is cached: feed our client with cached data */\n      ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData);\n      Cache_delayed_process_queue(entry);\n\n   } else {\n      /* URL not cached: create an entry, send our client to the queue,\n       * and open a new connection */\n      entry = Cache_entry_add(Url);\n      ClientKey = Cache_client_enqueue(entry->Url, Web, Call, CbData);\n   }\n\n   return ClientKey;\n}\n\n/*\n * Get cache entry status\n */\nuint_t a_Cache_get_flags(const DilloUrl *url)\n{\n   CacheEntry_t *entry = Cache_entry_search(url);\n   return (entry ? entry->Flags : 0);\n}\n\n/*\n * Get cache entry status (following redirections).\n */\nuint_t a_Cache_get_flags_with_redirection(const DilloUrl *url)\n{\n   CacheEntry_t *entry = Cache_entry_search_with_redirect(url);\n   return (entry ? entry->Flags : 0);\n}\n\n/*\n * Reference the cache data.\n */\nstatic void Cache_ref_data(CacheEntry_t *entry)\n{\n   if (entry) {\n      entry->DataRefcount++;\n      _MSG(\"DataRefcount++: %d\\n\", entry->DataRefcount);\n      if (entry->CharsetDecoder &&\n          (!entry->UTF8Data || entry->DataRefcount == 1)) {\n         dStr_free(entry->UTF8Data, 1);\n         entry->UTF8Data = a_Decode_process(entry->CharsetDecoder,\n                                            entry->Data->str,\n                                            entry->Data->len);\n      }\n   }\n}\n\n/*\n * Unreference the cache data.\n */\nstatic void Cache_unref_data(CacheEntry_t *entry)\n{\n   if (entry) {\n      entry->DataRefcount--;\n      _MSG(\"DataRefcount--: %d\\n\", entry->DataRefcount);\n\n      if (entry->CharsetDecoder) {\n         if (entry->DataRefcount == 0) {\n            dStr_free(entry->UTF8Data, 1);\n            entry->UTF8Data = NULL;\n         } else if (entry->DataRefcount < 0) {\n            MSG_ERR(\"Cache_unref_data: negative refcount\\n\");\n            entry->DataRefcount = 0;\n         }\n      }\n   }\n}\n\n/*\n * Get current content type.\n */\nstatic const char *Cache_current_content_type(CacheEntry_t *entry)\n{\n   return entry->TypeNorm ? entry->TypeNorm : entry->TypeMeta ? entry->TypeMeta\n          : entry->TypeHdr ? entry->TypeHdr : entry->TypeDet;\n}\n\n/*\n * Get current Content-Type for cache entry found by URL.\n */\nconst char *a_Cache_get_content_type(const DilloUrl *url)\n{\n   CacheEntry_t *entry = Cache_entry_search_with_redirect(url);\n\n   return (entry) ? Cache_current_content_type(entry) : NULL;\n}\n\n/*\n * Get pointer to entry's data.\n */\nstatic Dstr *Cache_data(CacheEntry_t *entry)\n{\n   return entry->UTF8Data ? entry->UTF8Data : entry->Data;\n}\n\n/*\n * Change Content-Type for cache entry found by url.\n * from = { \"http\" | \"meta\" }\n * Return new content type.\n */\nconst char *a_Cache_set_content_type(const DilloUrl *url, const char *ctype,\n                                     const char *from)\n{\n   const char *curr;\n   char *major, *minor, *charset;\n   CacheEntry_t *entry = Cache_entry_search(url);\n\n   dReturn_val_if_fail (entry != NULL, NULL);\n\n   _MSG(\"a_Cache_set_content_type {%s} {%s}\\n\", ctype, URL_STR(url));\n\n   curr = Cache_current_content_type(entry);\n   if (entry->TypeMeta || (*from == 'h' && entry->TypeHdr) ) {\n      /* Type is already been set. Do nothing.\n       * BTW, META overrides TypeHdr */\n   } else {\n      if (*from == 'h') {\n         /* Content-Type from HTTP header */\n         entry->TypeHdr = dStrdup(ctype);\n      } else {\n         /* Content-Type from META */\n         entry->TypeMeta = dStrdup(ctype);\n      }\n      if (a_Misc_content_type_cmp(curr, ctype)) {\n         /* ctype gives one different from current */\n         a_Misc_parse_content_type(ctype, &major, &minor, &charset);\n         if (*from == 'm' && charset &&\n             ((!major || !*major) && (!minor || !*minor))) {\n            /* META only gives charset; use detected MIME type too */\n            entry->TypeNorm = dStrconcat(entry->TypeDet, ctype, NULL);\n         } else if (*from == 'm' &&\n                    !dStrnAsciiCasecmp(ctype, \"text/xhtml\", 10)) {\n            /* WORKAROUND: doxygen uses \"text/xhtml\" in META */\n            entry->TypeNorm = dStrdup(entry->TypeDet);\n         }\n         if (charset) {\n            if (entry->CharsetDecoder)\n               a_Decode_free(entry->CharsetDecoder);\n            entry->CharsetDecoder = a_Decode_charset_init(charset);\n            curr = Cache_current_content_type(entry);\n\n            /* Invalidate UTF8Data */\n            dStr_free(entry->UTF8Data, 1);\n            entry->UTF8Data = NULL;\n         }\n         dFree(major); dFree(minor); dFree(charset);\n      }\n   }\n   return curr;\n}\n\n/*\n * Get the pointer to the URL document, and its size, from the cache entry.\n * Return: 1 cached, 0 not cached.\n */\nint a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize)\n{\n   CacheEntry_t *entry = Cache_entry_search_with_redirect(Url);\n   if (entry) {\n      Dstr *data;\n      Cache_ref_data(entry);\n      data = Cache_data(entry);\n      *PBuf = data->str;\n      *BufSize = data->len;\n   } else {\n      *PBuf = NULL;\n      *BufSize = 0;\n   }\n   return (entry ? 1 : 0);\n}\n\n/*\n * Unreference the data buffer when no longer using it.\n */\nvoid a_Cache_unref_buf(const DilloUrl *Url)\n{\n   Cache_unref_data(Cache_entry_search_with_redirect(Url));\n}\n\n\n/*\n * Extract a single field from the header, allocating and storing the value\n * in 'field'. ('fieldname' must not include the trailing ':')\n * Return a new string with the field-content if found (NULL on error)\n * (This function expects a '\\r'-stripped header, with one-line header fields)\n */\nstatic char *Cache_parse_field(const char *header, const char *fieldname)\n{\n   char *field;\n   uint_t i, j;\n\n   for (i = 0; header[i]; i++) {\n      /* Search fieldname */\n      for (j = 0; fieldname[j]; j++)\n        if (D_ASCII_TOLOWER(fieldname[j]) != D_ASCII_TOLOWER(header[i + j]))\n           break;\n      if (fieldname[j]) {\n         /* skip to next line */\n         for ( i += j; header[i] != '\\n'; i++);\n         continue;\n      }\n\n      i += j;\n      if (header[i] == ':') {\n        /* Field found! */\n        while (header[++i] == ' ' || header[i] == '\\t');\n        for (j = 0; header[i + j] != '\\n'; j++);\n        while (j && (header[i + j - 1] == ' ' || header[i + j - 1] == '\\t'))\n           j--;\n        field = dStrndup(header + i, j);\n        return field;\n      }\n      while (header[i] != '\\n') i++;\n   }\n   return NULL;\n}\n\n/*\n * Extract multiple fields from the header.\n */\nstatic Dlist *Cache_parse_multiple_fields(const char *header,\n                                          const char *fieldname)\n{\n   uint_t i, j;\n   Dlist *fields = dList_new(8);\n   char *field;\n\n   for (i = 0; header[i]; i++) {\n      /* Search fieldname */\n      for (j = 0; fieldname[j]; j++)\n         if (D_ASCII_TOLOWER(fieldname[j]) != D_ASCII_TOLOWER(header[i + j]))\n            break;\n      if (fieldname[j]) {\n         /* skip to next line */\n         for (i += j; header[i] != '\\n'; i++);\n         continue;\n      }\n\n      i += j;\n      if (header[i] == ':') {\n         /* Field found! */\n         while (header[++i] == ' ' || header[i] == '\\t');\n         for (j = 0; header[i + j] != '\\n'; j++);\n         while (j && (header[i + j - 1] == ' ' || header[i + j - 1] == '\\t'))\n            j--;\n         field = dStrndup(header + i, j);\n         dList_append(fields, field);\n      } else {\n         while (header[i] != '\\n') i++;\n      }\n   }\n\n   if (dList_length(fields) == 0) {\n      dList_free(fields);\n      fields = NULL;\n   }\n   return fields;\n}\n\n/*\n * Scan, allocate, and set things according to header info.\n * (This function needs the whole header to work)\n */\nstatic void Cache_parse_header(CacheEntry_t *entry)\n{\n   char *header = entry->Header->str;\n   bool_t server1point0 = !strncmp(entry->Header->str, \"HTTP/1.0\", 8);\n   char *Length, *Type, *location_str, *encoding, *connection, *hsts;\n#ifndef DISABLE_COOKIES\n   Dlist *Cookies;\n#endif\n   Dlist *warnings;\n   void *data;\n   int i;\n\n   _MSG(\"Cache_parse_header\\n\");\n\n   if (entry->Header->len > 12) {\n      if (header[9] == '1' && header[10] == '0' && header[11] == '0') {\n         /* 100: Continue. The \"real\" header has not come yet. */\n         MSG(\"An actual 100 Continue header!\\n\");\n         entry->Flags &= ~CA_GotHeader;\n         dStr_free(entry->Header, 1);\n         entry->Header = dStr_new(\"\");\n         return;\n      }\n      if (header[9] == '3' && header[10] == '0' &&\n          (location_str = Cache_parse_field(header, \"Location\"))) {\n         /* 30x: URL redirection */\n         entry->Location = a_Url_new(location_str, URL_STR_(entry->Url));\n\n         if (!a_Domain_permit(entry->Url, entry->Location) ||\n             (URL_FLAGS(entry->Location) & (URL_Post + URL_Get) &&\n              dStrAsciiCasecmp(URL_SCHEME(entry->Location), \"dpi\") == 0 &&\n              dStrAsciiCasecmp(URL_SCHEME(entry->Url), \"dpi\") != 0)) {\n            /* Domain test, and forbid dpi GET and POST from non dpi-generated\n             * urls.\n             */\n            MSG(\"Redirection not followed from %s to %s\\n\",\n                URL_HOST(entry->Url), URL_STR(entry->Location));\n         } else {\n            entry->Flags |= CA_Redirect;\n            if (header[11] == '1')\n               entry->Flags |= CA_ForceRedirect;  /* 301 Moved Permanently */\n            else if (header[11] == '2')\n               entry->Flags |= CA_TempRedirect;   /* 302 Temporary Redirect */\n         }\n         dFree(location_str);\n      } else if (strncmp(header + 9, \"401\", 3) == 0) {\n         entry->Auth =\n            Cache_parse_multiple_fields(header, \"WWW-Authenticate\");\n      } else if (strncmp(header + 9, \"404\", 3) == 0) {\n         entry->Flags |= CA_NotFound;\n      }\n   }\n\n   if ((warnings = Cache_parse_multiple_fields(header, \"Warning\"))) {\n      for (i = 0; (data = dList_nth_data(warnings, i)); ++i) {\n         MSG_HTTP(\"%s\\n\", (char *)data);\n         dFree(data);\n      }\n      dList_free(warnings);\n   }\n\n   if (server1point0)\n      entry->Flags &= ~CA_KeepAlive;\n\n   if ((connection = Cache_parse_field(header, \"Connection\"))) {\n      if (!dStrAsciiCasecmp(connection, \"close\"))\n         entry->Flags &= ~CA_KeepAlive;\n      else if (server1point0 && !dStrAsciiCasecmp(connection, \"keep-alive\"))\n         entry->Flags |= CA_KeepAlive;\n      dFree(connection);\n   }\n\n   if (prefs.http_strict_transport_security &&\n       !dStrAsciiCasecmp(URL_SCHEME(entry->Url), \"https\") &&\n       a_Url_host_type(URL_HOST(entry->Url)) == URL_HOST_NAME &&\n       (hsts = Cache_parse_field(header, \"Strict-Transport-Security\"))) {\n      a_Hsts_set(hsts, entry->Url);\n      dFree(hsts);\n   }\n\n   /*\n    * Get Transfer-Encoding and initialize decoder\n    */\n   encoding = Cache_parse_field(header, \"Transfer-Encoding\");\n   entry->TransferDecoder = a_Decode_transfer_init(encoding);\n\n\n   if ((Length = Cache_parse_field(header, \"Content-Length\")) != NULL) {\n      if (encoding) {\n         /*\n          * If Transfer-Encoding is present, Content-Length must be ignored.\n          * If the Transfer-Encoding is non-identity, it is an error.\n          */\n         if (dStrAsciiCasecmp(encoding, \"identity\"))\n            MSG_HTTP(\"Content-Length and non-identity Transfer-Encoding \"\n                     \"headers both present.\\n\");\n      } else {\n         entry->Flags |= CA_GotLength;\n         entry->ExpectedSize = MAX(strtol(Length, NULL, 10), 0);\n      }\n      dFree(Length);\n   }\n\n   dFree(encoding); /* free Transfer-Encoding */\n\n#ifndef DISABLE_COOKIES\n   if (prefs.use_cookies && (Cookies = Cache_parse_multiple_fields(header, \"Set-Cookie\"))) {\n      CacheClient_t *client;\n\n      for (i = 0; (client = dList_nth_data(ClientQueue, i)); ++i) {\n         if (client->Url == entry->Url) {\n            DilloWeb *web = client->Web;\n\n            if (!web->requester ||\n                a_Url_same_organization(entry->Url, web->requester)) {\n               /* If cookies are third party, don't even consider them. */\n               char *server_date = Cache_parse_field(header, \"Date\");\n\n               a_Cookies_set(Cookies, entry->Url, server_date);\n               dFree(server_date);\n               break;\n            }\n         }\n      }\n      for (i = 0; (data = dList_nth_data(Cookies, i)); ++i)\n         dFree(data);\n      dList_free(Cookies);\n   }\n#endif /* !DISABLE_COOKIES */\n\n   /*\n    * Get Content-Encoding and initialize decoder\n    */\n   encoding = Cache_parse_field(header, \"Content-Encoding\");\n   entry->ContentDecoder = a_Decode_content_init(encoding);\n   dFree(encoding);\n\n   if (entry->ExpectedSize > 0) {\n      if (entry->ExpectedSize > HUGE_FILESIZE) {\n         entry->Flags |= CA_HugeFile;\n      }\n      /* Avoid some reallocs. With MAX_INIT_BUF we avoid a SEGFAULT\n       * with huge files (e.g. iso files).\n       * Note: the buffer grows automatically. */\n      dStr_free(entry->Data, 1);\n      entry->Data = dStr_sized_new(MIN(entry->ExpectedSize, MAX_INIT_BUF));\n   }\n\n   /* Get Content-Type */\n   if ((Type = Cache_parse_field(header, \"Content-Type\"))) {\n      /* This HTTP Content-Type is not trusted. It's checked against real data\n       * in Cache_process_queue(); only then CA_GotContentType becomes true. */\n      a_Cache_set_content_type(entry->Url, Type, \"http\");\n      _MSG(\"TypeHdr  {%s} {%s}\\n\", Type, URL_STR(entry->Url));\n      _MSG(\"TypeMeta {%s}\\n\", entry->TypeMeta);\n      dFree(Type);\n   }\n   Cache_ref_data(entry);\n}\n\n/*\n * Consume bytes until the whole header is got (up to a \"\\r\\n\\r\\n\" sequence)\n * (Also unfold multi-line fields and strip '\\r' chars from header)\n */\nstatic int Cache_get_header(CacheEntry_t *entry,\n                            const char *buf, size_t buf_size)\n{\n   size_t N, i;\n   Dstr *hdr = entry->Header;\n\n   /* Header finishes when N = 2 */\n   N = (hdr->len && hdr->str[hdr->len - 1] == '\\n');\n   for (i = 0; i < buf_size && N < 2; ++i) {\n      if (buf[i] == '\\r' || !buf[i])\n         continue;\n      if (N == 1 && (buf[i] == ' ' || buf[i] == '\\t')) {\n         /* unfold multiple-line header */\n         _MSG(\"Multiple-line header!\\n\");\n         dStr_erase(hdr, hdr->len - 1, 1);\n      }\n      N = (buf[i] == '\\n') ? N + 1 : 0;\n      dStr_append_c(hdr, buf[i]);\n   }\n\n   if (N == 2) {\n      /* Got whole header */\n      _MSG(\"Header [buf_size=%d]\\n%s\", i, hdr->str);\n      entry->Flags |= CA_GotHeader;\n      dStr_fit(hdr);\n      /* Return number of header bytes in 'buf' [1 based] */\n      return i;\n   }\n   return 0;\n}\n\nstatic void Cache_finish_msg(CacheEntry_t *entry)\n{\n   if (!(entry->Flags & CA_InProgress)) {\n      /* already finished */\n      return;\n   }\n\n   if ((entry->ExpectedSize || entry->TransferSize) &&\n       entry->TypeHdr == NULL) {\n      MSG_HTTP(\"Message with a body lacked Content-Type header.\\n\");\n   }\n   if ((entry->Flags & CA_GotLength) &&\n       (entry->ExpectedSize != entry->TransferSize)) {\n      MSG_HTTP(\"Content-Length (%d) does NOT match message body (%d) for %s\\n\",\n               entry->ExpectedSize, entry->TransferSize, URL_STR_(entry->Url));\n   }\n   entry->Flags &= ~CA_InProgress;\n   if (entry->TransferDecoder) {\n      a_Decode_transfer_free(entry->TransferDecoder);\n      entry->TransferDecoder = NULL;\n   }\n   if (entry->ContentDecoder) {\n      a_Decode_free(entry->ContentDecoder);\n      entry->ContentDecoder = NULL;\n   }\n   dStr_fit(entry->Data);                /* fit buffer size! */\n\n   if ((entry = Cache_process_queue(entry))) {\n      if (entry->Flags & CA_GotHeader) {\n         Cache_unref_data(entry);\n      }\n   }\n}\n\n/*\n * Receive new data, update the reception buffer (for next read), update the\n * cache, and service the client queue.\n *\n * This function gets called whenever the IO has new data.\n *  'Op' is the operation to perform\n *  'VPtr' is a (void) pointer to the IO control structure\n */\nbool_t a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size,\n                            const DilloUrl *Url)\n{\n   int offset, len;\n   const char *str;\n   Dstr *dstr1, *dstr2, *dstr3;\n   bool_t done = FALSE;\n   CacheEntry_t *entry = Cache_entry_search(Url);\n\n   /* Assert a valid entry (not aborted) */\n   dReturn_val_if_fail (entry != NULL, FALSE);\n\n   _MSG(\"__a_Cache_process_dbuf__\\n\");\n\n   if (Op == IORead) {\n      /*\n       * Cache_get_header() will set CA_GotHeader if it has a full header, and\n       * Cache_parse_header() will unset it if the header ends being\n       * merely an informational response from the server (i.e., 100 Continue)\n       */\n      for (offset = 0; !(entry->Flags & CA_GotHeader) &&\n           (len = Cache_get_header(entry, buf + offset, buf_size - offset));\n           Cache_parse_header(entry) ) {\n         offset += len;\n      }\n\n      if (entry->Flags & CA_GotHeader) {\n         str = buf + offset;\n         len = buf_size - offset;\n         entry->TransferSize += len;\n         dstr1 = dstr2 = dstr3 = NULL;\n\n         /* Decode arrived data (<= 3 stages) */\n         if (entry->TransferDecoder) {\n            dstr1 = a_Decode_transfer_process(entry->TransferDecoder, str,len);\n            done = a_Decode_transfer_finished(entry->TransferDecoder);\n            str = dstr1->str;\n            len = dstr1->len;\n         }\n         if (entry->ContentDecoder) {\n            dstr2 = a_Decode_process(entry->ContentDecoder, str, len);\n            str = dstr2->str;\n            len = dstr2->len;\n         }\n         dStr_append_l(entry->Data, str, len);\n         if (entry->CharsetDecoder && entry->UTF8Data) {\n            dstr3 = a_Decode_process(entry->CharsetDecoder, str, len);\n            dStr_append_l(entry->UTF8Data, dstr3->str, dstr3->len);\n         }\n         dStr_free(dstr1, 1);\n         dStr_free(dstr2, 1);\n         dStr_free(dstr3, 1);\n\n         if (entry->Data->len)\n            entry->Flags &= ~CA_IsEmpty;\n\n         if ((entry->Flags & CA_GotLength) &&\n             (entry->TransferSize >= entry->ExpectedSize)) {\n            done = TRUE;\n         }\n         if (!(entry->Flags & CA_KeepAlive)) {\n            /* Let IOClose finish it later */\n            done = FALSE;\n         }\n\n         entry = Cache_process_queue(entry);\n\n         if (entry && done)\n            Cache_finish_msg(entry);\n      }\n   } else if (Op == IOClose) {\n      Cache_finish_msg(entry);\n   } else if (Op == IOAbort) {\n      entry->Flags |= CA_Aborted;\n      if (entry->Data->len) {\n         MSG(\"Premature close for %s\\n\", URL_STR(entry->Url));\n         Cache_finish_msg(entry);\n      } else {\n         int i;\n         CacheClient_t *Client;\n\n         for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) {\n            if (Client->Url == entry->Url) {\n               DilloWeb *web = (DilloWeb *)Client->Web;\n\n               a_Bw_remove_client(web->bw, Client->Key);\n               Cache_client_dequeue(Client);\n               --i; /* Keep the index value in the next iteration */\n            }\n         }\n      }\n   }\n   return done;\n}\n\n/*\n * Process redirections (HTTP 30x answers)\n * (This is a work in progress --not finished yet)\n */\nstatic int Cache_redirect(CacheEntry_t *entry, int Flags, BrowserWindow *bw)\n{\n   DilloUrl *NewUrl;\n\n   _MSG(\" Cache_redirect: redirect_level = %d\\n\", bw->redirect_level);\n\n   /* Don't allow redirection for SpamSafe/local URLs */\n   if (URL_FLAGS(entry->Url) & URL_SpamSafe) {\n      a_UIcmd_set_msg(bw, \"WARNING: local URL with redirection.  Aborting.\");\n      return 0;\n   }\n\n   /* if there's a redirect loop, stop now */\n   if (bw->redirect_level >= 5)\n      entry->Flags |= CA_RedirectLoop;\n\n   if (entry->Flags & CA_RedirectLoop) {\n      a_UIcmd_set_msg(bw, \"ERROR: redirect loop for: %s\", URL_STR_(entry->Url));\n      bw->redirect_level = 0;\n      return 0;\n   }\n\n   if ((entry->Flags & CA_Redirect && entry->Location) &&\n       (entry->Flags & CA_ForceRedirect || entry->Flags & CA_TempRedirect ||\n        !entry->Data->len || entry->Data->len < 1024)) {\n\n      _MSG(\">>>> Redirect from: %s\\n to %s <<<<\\n\",\n           URL_STR_(entry->Url), URL_STR_(entry->Location));\n      _MSG(\"%s\", entry->Header->str);\n\n      if (Flags & WEB_RootUrl) {\n         /* Redirection of the main page */\n         NewUrl = a_Url_new(URL_STR_(entry->Location), URL_STR_(entry->Url));\n         if (entry->Flags & CA_TempRedirect)\n            a_Url_set_flags(NewUrl, URL_FLAGS(NewUrl) | URL_E2EQuery);\n         a_Nav_push(bw, NewUrl, entry->Url);\n         a_Url_free(NewUrl);\n      } else {\n         /* Sub entity redirection (most probably an image) */\n         if (!entry->Data->len) {\n            _MSG(\">>>> Image redirection without entity-content <<<<\\n\");\n         } else {\n            _MSG(\">>>> Image redirection with entity-content <<<<\\n\");\n         }\n      }\n   }\n   return 0;\n}\n\ntypedef struct {\n   Dlist *auth;\n   DilloUrl *url;\n   BrowserWindow *bw;\n} CacheAuthData_t;\n\n/*\n * Ask for user/password and reload the page.\n */\nstatic void Cache_auth_callback(void *vdata)\n{\n   CacheAuthData_t *data = (CacheAuthData_t *)vdata;\n   if (a_Auth_do_auth(data->auth, data->url))\n      a_Nav_reload(data->bw);\n   Cache_auth_free(data->auth);\n   a_Url_free(data->url);\n   dFree(data);\n   Cache_auth_entry(NULL, NULL);\n   a_Timeout_remove();\n}\n\n/*\n * Set a timeout function to ask for user/password.\n */\nstatic void Cache_auth_entry(CacheEntry_t *entry, BrowserWindow *bw)\n{\n   static int busy = 0;\n   CacheAuthData_t *data;\n\n   if (!entry) {\n      busy = 0;\n   } else if (busy) {\n      MSG_WARN(\"Cache_auth_entry: caught busy!\\n\");\n   } else if (entry->Auth) {\n      busy = 1;\n      data = dNew(CacheAuthData_t, 1);\n      data->auth = entry->Auth;\n      data->url = a_Url_dup(entry->Url);\n      data->bw = bw;\n      entry->Auth = NULL;\n      a_Timeout_add(0.0, Cache_auth_callback, data);\n   }\n}\n\n/*\n * Check whether a URL scheme is downloadable.\n * Return: 1 enabled, 0 disabled.\n */\nint a_Cache_download_enabled(const DilloUrl *url)\n{\n   if (!dStrAsciiCasecmp(URL_SCHEME(url), \"http\") ||\n       !dStrAsciiCasecmp(URL_SCHEME(url), \"https\") ||\n       !dStrAsciiCasecmp(URL_SCHEME(url), \"ftp\") ||\n       !dStrAsciiCasecmp(URL_SCHEME(url), \"gemini\") ||\n       !dStrAsciiCasecmp(URL_SCHEME(url), \"gopher\"))\n      return 1;\n   return 0;\n}\n\n/*\n * Don't process data any further, but let the cache fill the entry.\n * (Currently used to handle WEB_RootUrl redirects,\n *  and to ignore image redirects --Jcid)\n */\nstatic void Cache_null_client(int Op, CacheClient_t *Client)\n{\n   DilloWeb *Web = Client->Web;\n\n   /* make the stop button insensitive when done */\n   if (Op == CA_Close) {\n      if (Web->flags & WEB_RootUrl) {\n         /* Remove this client from our active list */\n         a_Bw_close_client(Web->bw, Client->Key);\n      }\n   }\n\n   /* else ignore */\n\n   return;\n}\n\ntypedef struct {\n   BrowserWindow *bw;\n   DilloUrl *url;\n} Cache_savelink_t;\n\n/*\n * Save link from behind a timeout so that Cache_process_queue() can\n * get on with its work.\n */\nstatic void Cache_savelink_cb(void *vdata)\n{\n   Cache_savelink_t *data = (Cache_savelink_t*) vdata;\n\n   a_UIcmd_save_link(data->bw, data->url);\n   a_Url_free(data->url);\n   dFree(data);\n}\n\n/*\n * Let the client know that we're not following a redirection.\n */\nstatic void Cache_provide_redirection_blocked_page(CacheEntry_t *entry,\n                                                   CacheClient_t *client)\n{\n   DilloWeb *clientWeb = client->Web;\n\n   a_Web_dispatch_by_type(\"text/html\", clientWeb, &client->Callback,\n                          &client->CbData);\n   client->Buf = dStrconcat(\"<!doctype html><html><body>\"\n                    \"Dillo blocked a redirection from <a href=\\\"\",\n                    URL_STR(entry->Url), \"\\\">\", URL_STR(entry->Url),\n                    \"</a> to <a href=\\\"\", URL_STR(entry->Location), \"\\\">\",\n                    URL_STR(entry->Location), \"</a> based on your domainrc \"\n                    \"settings.</body></html>\", NULL);\n   client->BufSize = strlen(client->Buf);\n   (client->Callback)(CA_Send, client);\n   dFree(client->Buf);\n}\n\n/*\n * Update cache clients for a single cache-entry\n * Tasks:\n *   - Set the client function (if not already set)\n *   - Look if new data is available and pass it to client functions\n *   - Remove clients when done\n *   - Call redirect handler\n *\n * Return: Cache entry, which may be NULL if it has been removed.\n *\n * TODO: Implement CA_Abort Op in client callback\n */\nstatic CacheEntry_t *Cache_process_queue(CacheEntry_t *entry)\n{\n   uint_t i;\n   int st;\n   const char *Type;\n   Dstr *data;\n   CacheClient_t *Client;\n   DilloWeb *ClientWeb;\n   BrowserWindow *Client_bw = NULL;\n   static bool_t Busy = FALSE;\n   bool_t AbortEntry = FALSE;\n   bool_t OfferDownload = FALSE;\n   bool_t TypeMismatch = FALSE;\n\n   if (Busy)\n      MSG_ERR(\"FATAL!: >>>> Cache_process_queue Caught busy!!! <<<<\\n\");\n   if (!(entry->Flags & CA_GotHeader))\n      return entry;\n   if (!(entry->Flags & CA_GotContentType)) {\n      st = a_Misc_get_content_type_from_data(\n              entry->Data->str, entry->Data->len, &Type);\n      _MSG(\"Cache: detected Content-Type '%s'\\n\", Type);\n      if (st == 0 || !(entry->Flags & CA_InProgress)) {\n         if (a_Misc_content_type_check(entry->TypeHdr, Type) < 0) {\n            MSG_HTTP(\"Content-Type '%s' doesn't match the real data.\\n\",\n                     entry->TypeHdr);\n            TypeMismatch = TRUE;\n         }\n         entry->TypeDet = dStrdup(Type);\n         entry->Flags |= CA_GotContentType;\n      } else\n         return entry;  /* i.e., wait for more data */\n   }\n\n   Busy = TRUE;\n   for (i = 0; (Client = dList_nth_data(ClientQueue, i)); ++i) {\n      if (Client->Url == entry->Url) {\n         ClientWeb = Client->Web;    /* It was a (void*) */\n         Client_bw = ClientWeb->bw;  /* 'bw' in a local var */\n\n         if (ClientWeb->flags & WEB_RootUrl) {\n            if (!(entry->Flags & CA_MsgErased)) {\n               /* clear the \"expecting for reply...\" message */\n               a_UIcmd_set_msg(Client_bw, \"\");\n               entry->Flags |= CA_MsgErased;\n            }\n            if (TypeMismatch) {\n               a_UIcmd_set_msg(Client_bw,\"HTTP warning: Content-Type '%s' \"\n                               \"doesn't match the real data.\", entry->TypeHdr);\n               OfferDownload = TRUE;\n            }\n            if (entry->Flags & CA_Redirect) {\n               if (!Client->Callback) {\n                  Client->Callback = Cache_null_client;\n                  Client_bw->redirect_level++;\n               }\n            } else {\n               Client_bw->redirect_level = 0;\n            }\n            if (entry->Flags & CA_HugeFile) {\n               a_UIcmd_set_msg(Client_bw, \"Huge file! (%d MB)\",\n                               entry->ExpectedSize / (1024*1024));\n               AbortEntry = OfferDownload = TRUE;\n            }\n         } else {\n            /* For non root URLs, ignore redirections and 404 answers */\n            if (entry->Flags & CA_Redirect || entry->Flags & CA_NotFound)\n               Client->Callback = Cache_null_client;\n         }\n\n         /* Set the client function */\n         if (!Client->Callback) {\n            Client->Callback = Cache_null_client;\n\n            if (entry->Location && !(entry->Flags & CA_Redirect)) {\n               /* Not following redirection, so don't display page body. */\n            } else {\n               if (TypeMismatch) {\n                  AbortEntry = TRUE;\n               } else {\n                  const char *curr_type = Cache_current_content_type(entry);\n                  st = a_Web_dispatch_by_type(curr_type, ClientWeb,\n                                              &Client->Callback,\n                                              &Client->CbData);\n                  if (st == -1) {\n                     /* MIME type is not viewable */\n                     if (ClientWeb->flags & WEB_RootUrl) {\n                        MSG(\"Content-Type '%s' not viewable.\\n\", curr_type);\n                        /* prepare a download offer... */\n                        AbortEntry = OfferDownload = TRUE;\n                     } else {\n                        /* TODO: Resource Type not handled.\n                         * Not aborted to avoid multiple connections on the\n                         * same resource. A better idea is to abort the\n                         * connection and to keep a failed-resource flag in\n                         * the cache entry. */\n                     }\n                  }\n               }\n               if (AbortEntry) {\n                  if (ClientWeb->flags & WEB_RootUrl)\n                     a_Nav_cancel_expect_if_eq(Client_bw, Client->Url);\n                  a_Bw_remove_client(Client_bw, Client->Key);\n                  Cache_client_dequeue(Client);\n                  --i; /* Keep the index value in the next iteration */\n                  continue;\n               }\n            }\n         }\n\n         /* Send data to our client */\n         if (ClientWeb->flags & WEB_Download) {\n            /* for download, always provide original data, not translated */\n            data = entry->Data;\n         } else {\n            data = Cache_data(entry);\n         }\n         if ((Client->BufSize = data->len) > 0) {\n            Client->Buf = data->str;\n            (Client->Callback)(CA_Send, Client);\n            if (ClientWeb->flags & WEB_RootUrl) {\n               /* show size of page received */\n               a_UIcmd_set_page_prog(Client_bw, entry->Data->len, 1);\n            }\n         }\n\n         /* Remove client when done */\n         if (!(entry->Flags & CA_InProgress)) {\n            /* Copy flags to a local var */\n            int flags = ClientWeb->flags;\n\n            if (ClientWeb->flags & WEB_RootUrl && entry->Location &&\n                !(entry->Flags & CA_Redirect)) {\n               Cache_provide_redirection_blocked_page(entry, Client);\n            }\n            /* We finished sending data, let the client know */\n            (Client->Callback)(CA_Close, Client);\n            if (ClientWeb->flags & WEB_RootUrl) {\n               if (entry->Flags & CA_Aborted) {\n                  a_UIcmd_set_msg(Client_bw, \"ERROR: Connection closed early, \"\n                                             \"read not complete.\");\n               }\n               a_UIcmd_set_page_prog(Client_bw, 0, 0);\n            }\n            Cache_client_dequeue(Client);\n            --i; /* Keep the index value in the next iteration */\n\n            /* we assert just one redirect call */\n            if (entry->Flags & CA_Redirect)\n               Cache_redirect(entry, flags, Client_bw);\n         }\n      }\n   } /* for */\n\n   if (AbortEntry) {\n      /* Abort the entry, remove it from cache, and maybe offer download. */\n      DilloUrl *url = a_Url_dup(entry->Url);\n      a_Capi_conn_abort_by_url(url);\n      entry = NULL;\n      if (OfferDownload) {\n         /* Remove entry when 'conn' is already done */\n         Cache_entry_remove(NULL, url);\n         if (a_Cache_download_enabled(url)) {\n            Cache_savelink_t *data = dNew(Cache_savelink_t, 1);\n            data->bw = Client_bw;\n            data->url = a_Url_dup(url);\n            a_Timeout_add(0.0, Cache_savelink_cb, data);\n         }\n      }\n      a_Url_free(url);\n   } else if (entry->Auth && !(entry->Flags & CA_InProgress)) {\n      Cache_auth_entry(entry, Client_bw);\n   }\n\n   /* Trigger cleanup when there are no cache clients */\n   if (dList_length(ClientQueue) == 0) {\n      a_Dicache_cleanup();\n   }\n\n   Busy = FALSE;\n   _MSG(\"QueueSize ====> %d\\n\", dList_length(ClientQueue));\n   return entry;\n}\n\n/*\n * Callback function for Cache_delayed_process_queue.\n */\nstatic void Cache_delayed_process_queue_callback()\n{\n   CacheEntry_t *entry;\n\n   while ((entry = (CacheEntry_t *)dList_nth_data(DelayedQueue, 0))) {\n      Cache_ref_data(entry);\n      if ((entry = Cache_process_queue(entry))) {\n         Cache_unref_data(entry);\n         dList_remove(DelayedQueue, entry);\n      }\n   }\n   DelayedQueueIdleId = 0;\n   a_Timeout_remove();\n}\n\n/*\n * Set a call to Cache_process_queue from the main cycle.\n */\nstatic void Cache_delayed_process_queue(CacheEntry_t *entry)\n{\n   /* there's no need to repeat entries in the queue */\n   if (!dList_find(DelayedQueue, entry))\n      dList_append(DelayedQueue, entry);\n\n   if (DelayedQueueIdleId == 0) {\n      _MSG(\"  Setting timeout callback\\n\");\n      a_Timeout_add(0.0, Cache_delayed_process_queue_callback, NULL);\n      DelayedQueueIdleId = 1;\n   }\n}\n\n/*\n * Last Client for this entry?\n * Return: Client if true, NULL otherwise\n * (cache.c has only one call to a capi function. This avoids a second one)\n */\nCacheClient_t *a_Cache_client_get_if_unique(int Key)\n{\n   int i, n = 0;\n   CacheClient_t *Client, *iClient;\n\n   if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key),\n                                   Cache_client_by_key_cmp))) {\n      for (i = 0; (iClient = dList_nth_data(ClientQueue, i)); ++i) {\n         if (iClient->Url == Client->Url) {\n            ++n;\n         }\n      }\n   }\n   return (n == 1) ? Client : NULL;\n}\n\n/*\n * Remove a client from the client queue\n * TODO: notify the dicache and upper layers\n */\nvoid a_Cache_stop_client(int Key)\n{\n   CacheClient_t *Client;\n   CacheEntry_t *entry;\n   DICacheEntry *DicEntry;\n\n   /* The client can be in both queues at the same time */\n   if ((Client = dList_find_custom(ClientQueue, INT2VOIDP(Key),\n                                   Cache_client_by_key_cmp))) {\n      /* Dicache */\n      if ((DicEntry = a_Dicache_get_entry(Client->Url, Client->Version)))\n         a_Dicache_unref(Client->Url, Client->Version);\n\n      /* DelayedQueue */\n      if ((entry = Cache_entry_search(Client->Url)))\n         dList_remove(DelayedQueue, entry);\n\n      /* Main queue */\n      Cache_client_dequeue(Client);\n\n   } else {\n      _MSG(\"WARNING: Cache_stop_client, nonexistent client\\n\");\n   }\n}\n\n\n/*\n * Memory deallocator (only called at exit time)\n */\nvoid a_Cache_freeall(void)\n{\n   CacheClient_t *Client;\n   void *data;\n\n   /* free the client queue */\n   while ((Client = dList_nth_data(ClientQueue, 0)))\n      Cache_client_dequeue(Client);\n\n   /* Remove every cache entry */\n   while ((data = dList_nth_data(CachedURLs, 0))) {\n      dList_remove_fast(CachedURLs, data);\n      Cache_entry_free(data);\n   }\n   /* Remove the cache list */\n   dList_free(CachedURLs);\n}\n"
  },
  {
    "path": "src/cache.h",
    "content": "#ifndef __CACHE_H__\n#define __CACHE_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\n#include \"chain.h\"\n#include \"url.h\"\n\n/*\n * Cache Op codes\n */\n#define CA_Send    (0)  /* Normal update */\n#define CA_Close   (1)  /* Successful operation close */\n#define CA_Abort   (2)  /* Operation abort */\n\n/*\n * Flag Defines\n */\n#define CA_GotHeader       0x1  /* True after header is completely got */\n#define CA_GotContentType  0x2  /* True after Content-Type is known */\n#define CA_GotLength       0x4  /* True if Content-Length is known */\n#define CA_InProgress      0x8  /* True if we are getting data */\n#define CA_Redirect       0x10  /* Data actually points to a redirect */\n#define CA_ForceRedirect  0x20  /* Unconditional redirect */\n#define CA_TempRedirect   0x40  /* Temporary redirect */\n#define CA_NotFound       0x80  /* True if remote server didn't find the URL */\n#define CA_Aborted       0x100  /* Aborted before getting full data */\n#define CA_MsgErased     0x200  /* Used to erase the bw's status bar */\n#define CA_RedirectLoop  0x400  /* Redirect loop */\n#define CA_InternalUrl   0x800  /* URL content is generated by dillo */\n#define CA_HugeFile     0x1000  /* URL content is too big */\n#define CA_IsEmpty      0x2000  /* True until a byte of content arrives */\n#define CA_KeepAlive    0x4000\n\ntypedef struct CacheClient CacheClient_t;\n\n/*\n * Callback type for cache clients\n */\ntypedef void (*CA_Callback_t)(int Op, CacheClient_t *Client);\n\n/*\n * Data structure for cache clients.\n */\nstruct CacheClient {\n   int Key;                 /* Primary Key for this client */\n   const DilloUrl *Url;     /* Pointer to a cache entry Url */\n   int Version;             /* Dicache version of this Url (0 if not used) */\n   void *Buf;               /* Pointer to cache-data */\n   uint_t BufSize;          /* Valid size of cache-data */\n   CA_Callback_t Callback;  /* Client function */\n   void *CbData;            /* Client function data */\n   void *Web;               /* Pointer to the Web structure of our client */\n};\n\n/*\n * Function prototypes\n */\nvoid a_Cache_init(void);\nint a_Cache_open_url(void *Web, CA_Callback_t Call, void *CbData);\nint a_Cache_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize);\nvoid a_Cache_unref_buf(const DilloUrl *Url);\nconst char *a_Cache_get_content_type(const DilloUrl *url);\nconst char *a_Cache_set_content_type(const DilloUrl *url, const char *ctype,\n                                     const char *from);\nuint_t a_Cache_get_flags(const DilloUrl *url);\nuint_t a_Cache_get_flags_with_redirection(const DilloUrl *url);\nbool_t a_Cache_process_dbuf(int Op, const char *buf, size_t buf_size,\n                          const DilloUrl *Url);\nint a_Cache_download_enabled(const DilloUrl *url);\nvoid a_Cache_entry_remove_by_url(DilloUrl *url);\nvoid a_Cache_freeall(void);\nCacheClient_t *a_Cache_client_get_if_unique(int Key);\nvoid a_Cache_stop_client(int Key);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* __CACHE_H__ */\n\n"
  },
  {
    "path": "src/capi.c",
    "content": "/*\n * File: capi.c\n *\n * Copyright 2002-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Cache API\n * This is the module that manages the cache and starts the CCC chains\n * to get the requests served. Kind of a broker.\n */\n\n#include <string.h>\n#include <errno.h>\n\n#include \"config.h\"\n#include \"msg.h\"\n#include \"capi.h\"\n#include \"IO/IO.h\"    /* for IORead &friends */\n#include \"IO/Url.h\"\n#include \"chain.h\"\n#include \"history.h\"\n#include \"nav.h\"\n#include \"dpiapi.h\"\n#include \"uicmd.hh\"\n#include \"domain.h\"\n#include \"../dpip/dpip.h\"\n\n/* for testing dpi chat */\n#include \"bookmark.h\"\n\ntypedef struct {\n   DilloUrl *url;           /* local copy of web->url */\n   void *bw;\n   char *server;\n   char *datastr;\n   int SockFD;\n   int Flags;\n   ChainLink *InfoSend;\n   ChainLink *InfoRecv;\n\n   int Ref;\n} capi_conn_t;\n\n/* Flags for conn */\nenum {\n   PENDING = 1,\n   TIMEOUT = 2,  /* unused */\n   ABORTED = 4\n};\n\n/*\n * Local data\n */\n/* Data list for active dpi connections */\nstatic Dlist *CapiConns;      /* Data list for active connections; it holds\n                               * pointers to capi_conn_t structures. */\n/* Last URL asked for view source */\nstatic DilloUrl *CapiVsUrl = NULL;\n\n/*\n * Forward declarations\n */\nvoid a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,\n                void *Data1, void *Data2);\n\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Initialize capi&cache data\n */\nvoid a_Capi_init(void)\n{\n   /* create an empty list */\n   CapiConns = dList_new(32);\n   /* init cache */\n   a_Cache_init();\n}\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Create a new connection data structure\n */\nstatic capi_conn_t *\n Capi_conn_new(const DilloUrl *url, void *bw, char *server, char *datastr)\n{\n   capi_conn_t *conn;\n\n   conn = dNew(capi_conn_t, 1);\n   conn->url = url ? a_Url_dup(url) : NULL;\n   conn->bw = bw;\n   conn->server = dStrdup(server);\n   conn->datastr = dStrdup(datastr);\n   conn->SockFD = -1;\n   conn->Flags = (strcmp(server, \"http\") != 0) ? PENDING : 0;\n   conn->InfoSend = NULL;\n   conn->InfoRecv = NULL;\n   conn->Ref = 0;           /* Reference count */\n   return conn;\n}\n\n/*\n * Validate a capi_conn_t pointer.\n * Return value: NULL if not valid, conn otherwise.\n */\nstatic capi_conn_t *Capi_conn_valid(capi_conn_t *conn)\n{\n   return dList_find(CapiConns, conn);\n}\n\n/*\n * Increment the reference count and add to the list if not present\n */\nstatic void Capi_conn_ref(capi_conn_t *conn)\n{\n   if (++conn->Ref == 1) {\n      /* add the connection data to list */\n      dList_append(CapiConns, (void *)conn);\n   }\n   _MSG(\" Capi_conn_ref #%d %p\\n\", conn->Ref, conn);\n}\n\n/*\n * Decrement the reference count (and remove from list when zero)\n */\nstatic void Capi_conn_unref(capi_conn_t *conn)\n{\n   _MSG(\" Capi_conn_unref #%d %p\\n\", conn->Ref - 1, conn);\n\n   /* We may validate conn here, but it doesn't *seem* necessary */\n   if (--conn->Ref == 0) {\n      /* remove conn preserving the list order */\n      dList_remove(CapiConns, (void *)conn);\n      /* free dynamic memory */\n      a_Url_free(conn->url);\n      dFree(conn->server);\n      dFree(conn->datastr);\n      dFree(conn);\n   }\n   _MSG(\" Capi_conn_unref CapiConns=%d\\n\", dList_length(CapiConns));\n}\n\n/*\n * Compare function for searching a conn by server string\n */\nstatic int Capi_conn_by_server_cmp(const void *v1, const void *v2)\n{\n   const capi_conn_t *node = v1;\n   const char *server = v2;\n   dReturn_val_if_fail(node && node->server && server, 1);\n   return strcmp(node->server, server);\n}\n\n/*\n * Find connection data by server\n */\nstatic capi_conn_t *Capi_conn_find(char *server)\n{\n   return dList_find_custom(CapiConns, (void*)server, Capi_conn_by_server_cmp);\n}\n\n/*\n * Resume connections that were waiting for dpid to start.\n */\nstatic void Capi_conn_resume(void)\n{\n   int i;\n   DataBuf *dbuf;\n   capi_conn_t *conn;\n\n   for (i = 0; i < dList_length(CapiConns); ++i) {\n      conn = dList_nth_data (CapiConns, i);\n      if (conn->Flags & PENDING) {\n         dbuf = a_Chain_dbuf_new(conn->datastr,(int)strlen(conn->datastr), 0);\n         if (conn->InfoSend) {\n            a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL);\n         }\n         dFree(dbuf);\n         conn->Flags &= ~PENDING;\n      }\n   }\n}\n\n/*\n * Abort the connection for a given url, using its CCC.\n * (OpAbort 2,BCK removes the cache entry)\n * TODO: when conn is already done, the cache entry isn't removed.\n *       This may be wrong and needs a revision.\n */\nvoid a_Capi_conn_abort_by_url(const DilloUrl *url)\n{\n   int i;\n   capi_conn_t *conn;\n\n   for (i = 0; i < dList_length(CapiConns); ++i) {\n      conn = dList_nth_data (CapiConns, i);\n      if (a_Url_cmp(conn->url, url) == 0) {\n         if (conn->InfoSend) {\n            a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL);\n         }\n         if (conn->InfoRecv) {\n            a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL);\n         }\n         break;\n      }\n   }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Store the last URL requested by \"view source\"\n */\nvoid a_Capi_set_vsource_url(const DilloUrl *url)\n{\n   a_Url_free(CapiVsUrl);\n   CapiVsUrl = a_Url_dup(url);\n}\n\n/*\n * Safety test: only allow GET|POST dpi-urls from dpi-generated pages.\n */\nint a_Capi_dpi_verify_request(BrowserWindow *bw, DilloUrl *url)\n{\n   const DilloUrl *referer;\n   int allow = FALSE;\n\n   if (dStrAsciiCasecmp(URL_SCHEME(url), \"dpi\") == 0) {\n      if (!(URL_FLAGS(url) & (URL_Post + URL_Get))) {\n         allow = TRUE;\n      } else if (!(URL_FLAGS(url) & URL_Post) &&\n                 strncmp(URL_PATH(url), \"/vsource/\", 9) == 0) {\n         allow = TRUE;\n      } else {\n         /* only allow GET&POST dpi-requests from dpi-generated urls */\n         if (a_Nav_stack_size(bw)) {\n            referer = a_History_get_url(NAV_TOP_UIDX(bw));\n            if (dStrAsciiCasecmp(URL_SCHEME(referer), \"dpi\") == 0) {\n               allow = TRUE;\n            }\n         }\n      }\n   } else {\n      allow = TRUE;\n   }\n\n   if (!allow) {\n      MSG(\"a_Capi_dpi_verify_request: Permission Denied!\\n\");\n      MSG(\"  URL_STR : %s\\n\", URL_STR(url));\n      if (URL_FLAGS(url) & URL_Post) {\n         MSG(\"  URL_DATA: %s\\n\", dStr_printable(URL_DATA(url), 1024));\n      }\n   }\n   return allow;\n}\n\n/*\n * If the url belongs to a dpi server, return its name.\n */\nstatic int Capi_url_uses_dpi(DilloUrl *url, char **server_ptr)\n{\n   char *p, *server = NULL, *url_str = URL_STR(url);\n   Dstr *tmp;\n\n   if ((dStrnAsciiCasecmp(url_str, \"http:\", 5) == 0) ||\n       (dStrnAsciiCasecmp(url_str, \"https:\", 6) == 0) ||\n       (dStrnAsciiCasecmp(url_str, \"about:\", 6) == 0)) {\n      /* URL doesn't use dpi (server = NULL) */\n   } else if (dStrnAsciiCasecmp(url_str, \"dpi:/\", 5) == 0) {\n      /* dpi prefix, get this server's name */\n      if ((p = strchr(url_str + 5, '/')) != NULL) {\n         server = dStrndup(url_str + 5, (uint_t)(p - url_str - 5));\n      } else {\n         server = dStrdup(\"?\");\n      }\n      if (strcmp(server, \"bm\") == 0) {\n         dFree(server);\n         server = dStrdup(\"bookmarks\");\n      }\n   } else if ((p = strchr(url_str, ':')) != NULL) {\n      tmp = dStr_new(\"proto.\");\n      dStr_append_l(tmp, url_str, p - url_str);\n      server = tmp->str;\n      dStr_free(tmp, 0);\n   }\n\n   return ((*server_ptr = server) ? 1 : 0);\n}\n\n/*\n * Build the dpip command tag, according to URL and server.\n */\nstatic char *Capi_dpi_build_cmd(DilloWeb *web, char *server)\n{\n   char *cmd;\n\n   if (strcmp(server, \"downloads\") == 0) {\n      /* let the downloads server get it */\n      cmd = a_Dpip_build_cmd(\"cmd=%s user_agent=%s url=%s destination=%s\",\n                             \"download\", prefs.http_user_agent, URL_STR(web->url), web->filename);\n\n   } else {\n      /* For everyone else, the url string is enough... */\n      cmd = a_Dpip_build_cmd(\"cmd=%s url=%s\", \"open_url\", URL_STR(web->url));\n   }\n   return cmd;\n}\n\n/*\n * Send the requested URL's source to the \"view source\" dpi\n */\nstatic void Capi_dpi_send_source(BrowserWindow *bw, DilloUrl *url)\n{\n   char *p, *buf, *cmd, size_str[32], *server=\"vsource\";\n   int buf_size;\n\n   if (!(p = strchr(URL_STR(url), ':')) || !(p = strchr(p + 1, ':')))\n      return;\n\n   if (a_Capi_get_buf(CapiVsUrl, &buf, &buf_size)) {\n      /* send the page's source to this dpi connection */\n      snprintf(size_str, 32, \"%d\", buf_size);\n      cmd = a_Dpip_build_cmd(\"cmd=%s url=%s data_size=%s\",\n                             \"start_send_page\", URL_STR(url), size_str);\n      a_Capi_dpi_send_cmd(NULL, bw, cmd, server, 0);\n      a_Capi_dpi_send_data(url, bw, buf, buf_size, server, 0);\n   } else {\n      cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\",\n                             \"DpiError\", \"Page is NOT cached\");\n      a_Capi_dpi_send_cmd(NULL, bw, cmd, server, 0);\n   }\n   dFree(cmd);\n}\n\n/*\n * Shall we permit this request to open a URL?\n */\nstatic bool_t Capi_request_permitted(DilloWeb *web)\n{\n   bool_t permit = FALSE;\n\n   /* web->requester is NULL if the action is initiated by user */\n   if (!web->requester)\n      return TRUE;\n\n   if (web->flags & ~WEB_RootUrl &&\n       !dStrAsciiCasecmp(URL_SCHEME(web->requester), \"https\")) {\n      const char *s = URL_SCHEME(web->url);\n\n      /* As of 2015, blocking of \"active\" mixed content is widespread\n       * (style sheets, javascript, fonts, etc.), but the big browsers aren't\n       * quite in a position to block \"passive\" mixed content (images) yet.\n       * (Not clear whether there's consensus on which category to place\n       * background images in.)\n       *\n       * We are blocking both, and only permitting secure->insecure page\n       * redirection for now (e.g., duckduckgo has been seen providing links\n       * to https URLs that redirect to http). As the web security landscape\n       * evolves, we may be able to remove that permission.\n       */\n      if (dStrAsciiCasecmp(s, \"https\") && dStrAsciiCasecmp(s, \"data\")) {\n         MSG(\"capi: Blocked mixed content: %s -> %s\\n\",\n             URL_STR(web->requester), URL_STR(web->url));\n         return FALSE;\n      }\n   }\n\n   if (a_Capi_get_flags(web->url) & CAPI_IsCached ||\n       a_Domain_permit(web->requester, web->url)) {\n      permit = TRUE;\n   }\n   return permit;\n}\n\n/*\n * Most used function for requesting a URL.\n * TODO: clean up the ad-hoc bindings with an API that allows dynamic\n *       addition of new plugins.\n *\n * Return value: A primary key for identifying the client,\n *               0 if the client is aborted in the process.\n */\nint a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData)\n{\n   int reload;\n   char *cmd, *server;\n   capi_conn_t *conn = NULL;\n   const char *scheme = URL_SCHEME(web->url);\n   int safe = 0, ret = 0, use_cache = 0;\n\n   if (Capi_request_permitted(web)) {\n      /* reload test */\n      reload = (!(a_Capi_get_flags(web->url) & CAPI_IsCached) ||\n                (URL_FLAGS(web->url) & URL_E2EQuery));\n\n      if (web->flags & WEB_Download) {\n         /* download request: if cached save from cache, else\n          * for http, ftp or https, use the downloads dpi */\n        if (a_Capi_get_flags_with_redirection(web->url) & CAPI_IsCached) {\n           if (web->filename) {\n              if ((web->stream = fopen(web->filename, \"w\"))) {\n                 use_cache = 1;\n              } else {\n                 MSG_WARN(\"Cannot open \\\"%s\\\" for writing: %s.\\n\",\n                          web->filename, dStrerror(errno));\n              }\n           }\n        } else if (a_Cache_download_enabled(web->url)) {\n           server = \"downloads\";\n           cmd = Capi_dpi_build_cmd(web, server);\n           a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1);\n           dFree(cmd);\n        } else {\n           MSG_WARN(\"Ignoring download request for '%s': \"\n                    \"not in cache and not downloadable.\\n\",\n                    URL_STR(web->url));\n        }\n\n      } else if (Capi_url_uses_dpi(web->url, &server)) {\n         /* dpi request */\n         if ((safe = a_Capi_dpi_verify_request(web->bw, web->url))) {\n            if (dStrAsciiCasecmp(scheme, \"dpi\") == 0) {\n               if (strcmp(server, \"vsource\") == 0) {\n                  /* allow \"view source\" reload upon user request */\n               } else {\n                  /* make the other \"dpi:/\" prefixed urls always reload. */\n                  a_Url_set_flags(web->url, URL_FLAGS(web->url) |URL_E2EQuery);\n                  reload = 1;\n               }\n            }\n            if (reload) {\n               a_Capi_conn_abort_by_url(web->url);\n               /* Send dpip command */\n               _MSG(\"a_Capi_open_url, reload url='%s'\\n\", URL_STR(web->url));\n               cmd = Capi_dpi_build_cmd(web, server);\n               a_Capi_dpi_send_cmd(web->url, web->bw, cmd, server, 1);\n               dFree(cmd);\n               if (strcmp(server, \"vsource\") == 0) {\n                  Capi_dpi_send_source(web->bw, web->url);\n               }\n            }\n            use_cache = 1;\n         }\n         dFree(server);\n\n      } else if (!dStrAsciiCasecmp(scheme, \"http\") ||\n                 !dStrAsciiCasecmp(scheme, \"https\")) {\n         /* http request */\n\n#ifndef ENABLE_SSL\n         if (!dStrAsciiCasecmp(scheme, \"https\")) {\n            if (web->flags & WEB_RootUrl)\n               a_UIcmd_set_msg(web->bw,\n                               \"HTTPS was disabled at compilation time.\");\n            a_Web_free(web);\n            return 0;\n         }\n#endif\n         if (reload) {\n            a_Capi_conn_abort_by_url(web->url);\n            /* create a new connection and start the CCC operations */\n            conn = Capi_conn_new(web->url, web->bw, \"http\", \"none\");\n            /* start the reception branch before the query one because the DNS\n             * may callback immediately. This may avoid a race condition. */\n            a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, \"http\");\n            a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, web);\n         }\n         use_cache = 1;\n\n      } else if (!dStrAsciiCasecmp(scheme, \"about\")) {\n         /* internal request */\n         use_cache = 1;\n      }\n   }\n\n   if (use_cache) {\n      if (!conn || (conn && Capi_conn_valid(conn))) {\n         /* not aborted, let's continue... */\n         ret = a_Cache_open_url(web, Call, CbData);\n      }\n   } else {\n      a_Web_free(web);\n   }\n   return ret;\n}\n\n/*\n * Convert cache-defined flags to Capi ones.\n */\nstatic int Capi_map_cache_flags(uint_t flags)\n{\n   int status = 0;\n\n   if (flags) {\n      status |= CAPI_IsCached;\n      if (flags & CA_IsEmpty)\n         status |= CAPI_IsEmpty;\n      if (flags & CA_InProgress)\n         status |= CAPI_InProgress;\n      else\n         status |= CAPI_Completed;\n\n      /* CAPI_Aborted is not yet used/defined */\n   }\n   return status;\n}\n\n/*\n * Return status information of an URL's content-transfer process.\n */\nint a_Capi_get_flags(const DilloUrl *Url)\n{\n   uint_t flags = a_Cache_get_flags(Url);\n   int status = flags ? Capi_map_cache_flags(flags) : 0;\n   return status;\n}\n\n/*\n * Same as a_Capi_get_flags() but following redirections.\n */\nint a_Capi_get_flags_with_redirection(const DilloUrl *Url)\n{\n   uint_t flags = a_Cache_get_flags_with_redirection(Url);\n   int status = flags ? Capi_map_cache_flags(flags) : 0;\n   return status;\n}\n\n/*\n * Get the cache's buffer for the URL, and its size.\n * Return: 1 cached, 0 not cached.\n */\nint a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize)\n{\n   return a_Cache_get_buf(Url, PBuf, BufSize);\n}\n\n/*\n * Unref the cache's buffer when no longer using it.\n */\nvoid a_Capi_unref_buf(const DilloUrl *Url)\n{\n   a_Cache_unref_buf(Url);\n}\n\n/*\n * Get the Content-Type associated with the URL\n */\nconst char *a_Capi_get_content_type(const DilloUrl *url)\n{\n   return a_Cache_get_content_type(url);\n}\n\n/*\n * Set the Content-Type for the URL.\n */\nconst char *a_Capi_set_content_type(const DilloUrl *url, const char *ctype,\n                                    const char *from)\n{\n   return a_Cache_set_content_type(url, ctype, from);\n}\n\n/*\n * Send data to a dpi (e.g. add_bookmark, open_url, send_preferences, ...)\n * Most of the time we send dpi commands, but it also serves for raw data\n * as with \"view source\".\n */\nint a_Capi_dpi_send_data(const DilloUrl *url, void *bw,\n                         char *data, int data_sz, char *server, int flags)\n{\n   capi_conn_t *conn;\n   DataBuf *dbuf;\n\n   if (flags & 1) {\n      /* open a new connection to server */\n\n      /* Create a new connection data struct and add it to the list */\n      conn = Capi_conn_new(url, bw, server, data);\n      /* start the CCC operations */\n      a_Capi_ccc(OpStart, 2, BCK, a_Chain_new(), conn, server);\n      a_Capi_ccc(OpStart, 1, BCK, a_Chain_new(), conn, server);\n\n   } else {\n      /* Re-use an open connection */\n      conn = Capi_conn_find(server);\n      if (conn && conn->InfoSend) {\n         /* found & active */\n         dbuf = a_Chain_dbuf_new(data, data_sz, 0);\n         a_Capi_ccc(OpSend, 1, BCK, conn->InfoSend, dbuf, NULL);\n         dFree(dbuf);\n      } else {\n         MSG(\" ERROR: [a_Capi_dpi_send_data] No open connection found\\n\");\n      }\n   }\n\n   return 0;\n}\n\n/*\n * Send a dpi cmd.\n * (For instance: add_bookmark, open_url, send_preferences, ...)\n */\nint a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server,\n                        int flags)\n{\n   return a_Capi_dpi_send_data(url, bw, cmd, strlen(cmd), server, flags);\n}\n\n/*\n * Remove a client from the cache client queue.\n * force = also abort the CCC if this is the last client.\n */\nvoid a_Capi_stop_client(int Key, int force)\n{\n   CacheClient_t *Client;\n\n   _MSG(\"a_Capi_stop_client:  force=%d\\n\", force);\n\n   Client = a_Cache_client_get_if_unique(Key);\n   if (Client && (force || Client->BufSize == 0)) {\n      /* remove empty entries too */\n      a_Capi_conn_abort_by_url(Client->Url);\n   }\n   a_Cache_stop_client(Key);\n}\n\n/*\n * CCC function for the CAPI module\n */\nvoid a_Capi_ccc(int Op, int Branch, int Dir, ChainLink *Info,\n                void *Data1, void *Data2)\n{\n   capi_conn_t *conn;\n\n   dReturn_if_fail( a_Chain_check(\"a_Capi_ccc\", Op, Branch, Dir, Info) );\n\n   if (Branch == 1) {\n      if (Dir == BCK) {\n         /* Command sending branch */\n         switch (Op) {\n         case OpStart:\n            /* Data1 = conn; Data2 = {Web | server} */\n            conn = Data1;\n            Capi_conn_ref(conn);\n            Info->LocalKey = conn;\n            conn->InfoSend = Info;\n            if (strcmp(conn->server, \"http\") == 0 ||\n                strcmp(conn->server, \"https\") == 0) {\n               a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 1, 1);\n               a_Chain_bcb(OpStart, Info, Data2, NULL);\n            } else {\n               a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 1, 1);\n               a_Chain_bcb(OpStart, Info, Data2, NULL);\n            }\n            break;\n         case OpSend:\n            /* Data1 = dbuf */\n            a_Chain_bcb(OpSend, Info, Data1, NULL);\n            break;\n         case OpEnd:\n            conn = Info->LocalKey;\n            conn->InfoSend = NULL;\n            a_Chain_bcb(OpEnd, Info, NULL, NULL);\n            Capi_conn_unref(conn);\n            dFree(Info);\n            break;\n         case OpAbort:\n            conn = Info->LocalKey;\n            conn->InfoSend = NULL;\n            a_Chain_bcb(OpAbort, Info, NULL, NULL);\n            Capi_conn_unref(conn);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC Capi 1B\\n\");\n            break;\n         }\n      } else {  /* 1 FWD */\n         /* Command sending branch (status) */\n         switch (Op) {\n         case OpSend:\n            if (!Data2) {\n               MSG_WARN(\"Capi.c: Opsend [1F] Data2 = NULL\\n\");\n            } else if (strcmp(Data2, \"FD\") == 0) {\n               conn = Info->LocalKey;\n               conn->SockFD = *(int*)Data1;\n               /* communicate the FD through the answer branch */\n               a_Capi_ccc(OpSend, 2, BCK, conn->InfoRecv, &conn->SockFD, \"FD\");\n            } else if (strcmp(Data2, \"DpidOK\") == 0) {\n               /* resume pending dpi requests */\n               Capi_conn_resume();\n            }\n            break;\n         case OpAbort:\n            conn = Info->LocalKey;\n            conn->InfoSend = NULL;\n            a_Cache_process_dbuf(IOAbort, NULL, 0, conn->url);\n            if (Data2) {\n               if (!strcmp(Data2, \"DpidERROR\")) {\n                  a_UIcmd_set_msg(conn->bw,\n                                  \"ERROR: can't start dpid daemon \"\n                                  \"(URL scheme = '%s')!\",\n                                  conn->url ? URL_SCHEME(conn->url) : \"\");\n               } else if (!strcmp(Data2, \"Both\") && conn->InfoRecv) {\n                  /* abort the other branch too */\n                  a_Capi_ccc(OpAbort, 2, BCK, conn->InfoRecv, NULL, NULL);\n               }\n            }\n            /* if URL == expect-url */\n            a_Nav_cancel_expect_if_eq(conn->bw, conn->url);\n            /* finish conn */\n            Capi_conn_unref(conn);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC Capi 1F\\n\");\n            break;\n         }\n      }\n\n   } else if (Branch == 2) {\n      if (Dir == BCK) {\n         /* Answer branch */\n         switch (Op) {\n         case OpStart:\n            /* Data1 = conn; Data2 = {\"http\" | \"<dpi server name>\"} */\n            conn = Data1;\n            Capi_conn_ref(conn);\n            Info->LocalKey = conn;\n            conn->InfoRecv = Info;\n            if (strcmp(conn->server, \"http\") == 0)\n               a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Http_ccc, 2, 2);\n            else\n               a_Chain_link_new(Info, a_Capi_ccc, BCK, a_Dpi_ccc, 2, 2);\n            a_Chain_bcb(OpStart, Info, NULL, Data2);\n            break;\n         case OpSend:\n            /* Data1 = FD */\n            if (Data2 && strcmp(Data2, \"FD\") == 0) {\n               a_Chain_bcb(OpSend, Info, Data1, Data2);\n            }\n            break;\n         case OpAbort:\n            conn = Info->LocalKey;\n            conn->InfoRecv = NULL;\n            a_Chain_bcb(OpAbort, Info, NULL, NULL);\n            /* remove the cache entry for this URL */\n            a_Cache_entry_remove_by_url(conn->url);\n            Capi_conn_unref(conn);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC Capi 2B\\n\");\n            break;\n         }\n      } else {  /* 2 FWD */\n         /* Server listening branch */\n         switch (Op) {\n         case OpSend:\n            conn = Info->LocalKey;\n            if (strcmp(Data2, \"send_page_2eof\") == 0) {\n               /* Data1 = dbuf */\n               DataBuf *dbuf = Data1;\n               bool_t finished = a_Cache_process_dbuf(IORead, dbuf->Buf,\n                                                      dbuf->Size, conn->url);\n               if (finished && Capi_conn_valid(conn) && conn->InfoRecv) {\n                  /* If we have a persistent connection where cache tells us\n                   * that we've received the full response, and cache didn't\n                   * trigger an abort and tear everything down, tell upstream.\n                   */\n                  a_Chain_bcb(OpSend, conn->InfoRecv, NULL, \"reply_complete\");\n               }\n            } else if (strcmp(Data2, \"send_status_message\") == 0) {\n               a_UIcmd_set_msg(conn->bw, \"%s\", Data1);\n            } else if (strcmp(Data2, \"chat\") == 0) {\n               a_UIcmd_set_msg(conn->bw, \"%s\", Data1);\n               a_Bookmarks_chat_add(NULL, NULL, Data1);\n            } else if (strcmp(Data2, \"dialog\") == 0) {\n               a_Dpiapi_dialog(conn->bw, conn->server, Data1);\n            } else if (strcmp(Data2, \"reload_request\") == 0) {\n               a_Nav_reload(conn->bw);\n            } else if (strcmp(Data2, \"start_send_page\") == 0) {\n               /* prepare the cache to receive the data stream for this URL\n                *\n                * a_Capi_open_url() already added a new cache entry,\n                * and a client for it.\n                */\n            }\n            break;\n         case OpEnd:\n            conn = Info->LocalKey;\n            conn->InfoRecv = NULL;\n\n            a_Cache_process_dbuf(IOClose, NULL, 0, conn->url);\n\n            if (conn->InfoSend) {\n               /* Propagate OpEnd to the sending branch too */\n               a_Capi_ccc(OpEnd, 1, BCK, conn->InfoSend, NULL, NULL);\n            }\n            Capi_conn_unref(conn);\n            dFree(Info);\n            break;\n         case OpAbort:\n            conn = Info->LocalKey;\n            conn->InfoRecv = NULL;\n            a_Cache_process_dbuf(IOAbort, NULL, 0, conn->url);\n            if (Data2) {\n               if (!strcmp(Data2, \"Both\") && conn->InfoSend) {\n                  /* abort the other branch too */\n                  a_Capi_ccc(OpAbort, 1, BCK, conn->InfoSend, NULL, NULL);\n               }\n            }\n            /* if URL == expect-url */         \n            a_Nav_cancel_expect_if_eq(conn->bw, conn->url);\n            /* finish conn */\n            Capi_conn_unref(conn);\n            dFree(Info);\n            break;\n         default:\n            MSG_WARN(\"Unused CCC Capi 2F\\n\");\n            break;\n         }\n      }\n   }\n}\n"
  },
  {
    "path": "src/capi.h",
    "content": "#ifndef __CAPI_H__\n#define __CAPI_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\n#include \"cache.h\"\n#include \"web.hh\"\n\n/*\n * Flag defines\n */\n#define CAPI_IsCached       (0x1)\n#define CAPI_IsEmpty        (0x2)\n#define CAPI_InProgress     (0x4)\n#define CAPI_Aborted        (0x8)\n#define CAPI_Completed     (0x10)\n\n/*\n * Function prototypes\n */\nvoid a_Capi_init(void);\nint a_Capi_open_url(DilloWeb *web, CA_Callback_t Call, void *CbData);\nint a_Capi_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize);\nvoid a_Capi_unref_buf(const DilloUrl *Url);\nconst char *a_Capi_get_content_type(const DilloUrl *url);\nconst char *a_Capi_set_content_type(const DilloUrl *url, const char *ctype,\n                                    const char *from);\nint a_Capi_get_flags(const DilloUrl *Url);\nint a_Capi_get_flags_with_redirection(const DilloUrl *Url);\nint a_Capi_dpi_verify_request(BrowserWindow *bw, DilloUrl *url);\nint a_Capi_dpi_send_data(const DilloUrl *url, void *bw,\n                         char *data, int data_sz, char *server, int flags);\nint a_Capi_dpi_send_cmd(DilloUrl *url, void *bw, char *cmd, char *server,\n                         int flags);\nvoid a_Capi_set_vsource_url(const DilloUrl *url);\nvoid a_Capi_stop_client(int Key, int force);\nvoid a_Capi_conn_abort_by_url(const DilloUrl *url);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __CAPI_H__ */\n\n"
  },
  {
    "path": "src/chain.c",
    "content": "/*\n * File: chain.c\n * Concomitant control chain (CCC)\n * Theory and code by Jorge Arellano Cid\n *\n * Copyright 2001-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include \"msg.h\"\n#include \"chain.h\"\n#include \"../dlib/dlib.h\"\n\n#define VERBOSE 0\n\n/*\n * Show debugging info\n */\n#if VERBOSE\nstatic void Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir,\n                            ChainLink *Info)\n{\n   const char *StrOps[] = {\"\", \"OpStart\", \"OpSend\",\n                            \"OpStop\", \"OpEnd\", \"OpAbort\"};\n   MSG(\"%-*s: %-*s [%d%s] Info=%p Flags=%d\\n\",\n       12, FuncStr, 7, StrOps[Op], Branch, (Dir == 1) ? \"F\" : \"B\",\n       Info, Info ? Info->Flags : -1);\n}\n#else\nstatic void Chain_debug_msg(char *FuncStr, int Op, int Branch, int Dir,\n                            ChainLink *Info) { }\n#endif\n/*\n * Create and initialize a new chain-link\n */\nChainLink *a_Chain_new(void)\n{\n   return dNew0(ChainLink, 1);\n}\n\n/*\n * Create a new link from module A to module B.\n * 'Direction' tells whether to make a forward or backward link.\n * => The link from 'A' to 'B' has 'Direction' direction.\n * => The main flow of information names the FWD direction.\n * => AtoB_branch: branch on which 'B' receives communications from 'A'\n * => BtoA_branch: branch on which 'A' receives communications from 'B'\n */\nChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc,\n                            int Direction, ChainFunction_t BFunc,\n                            int AtoB_branch, int BtoA_branch)\n{\n   ChainLink *NewLink = a_Chain_new();\n   ChainLink *OldLink = AInfo;\n\n   if (Direction == BCK) {\n      NewLink->Fcb       = AFunc;\n      NewLink->FcbInfo   = AInfo;\n      NewLink->FcbBranch = BtoA_branch;\n      OldLink->Bcb       = BFunc;\n      OldLink->BcbInfo   = NewLink;\n      OldLink->BcbBranch = AtoB_branch;\n\n   } else { /* FWD */\n      NewLink->Bcb       = AFunc;\n      NewLink->BcbInfo   = AInfo;\n      NewLink->BcbBranch = BtoA_branch;\n      OldLink->Fcb       = BFunc;\n      OldLink->FcbInfo   = NewLink;\n      OldLink->FcbBranch = AtoB_branch;\n   }\n\n   return NewLink;\n}\n\n/*\n * Unlink a previously used link.\n * 'Direction' tells whether to unlink the forward or backward link.\n */\nvoid a_Chain_unlink(ChainLink *Info, int Direction)\n{\n   if (Direction == FWD) {\n      Info->Fcb = NULL;\n      Info->FcbInfo = NULL;\n      Info->FcbBranch = 0;\n   } else {      /* BCK */\n      Info->Bcb = NULL;\n      Info->BcbInfo = NULL;\n      Info->BcbBranch = 0;\n   }\n}\n\n/*\n * Issue the forward callback of the 'Info' link\n * Return value: 1 if OK, 0 if not operative.\n */\nint a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2)\n{\n   int ret = 0;\n\n   if (Info->Flags & (CCC_Ended + CCC_Aborted)) {\n      /* CCC is not operative */\n   } else if (Info->Fcb) {\n      /* flag the caller */\n      if (Op == OpEnd)\n         Info->Flags |= CCC_Ended;\n      else if (Op == OpAbort)\n         Info->Flags |= CCC_Aborted;\n\n      Info->Fcb(Op, Info->FcbBranch, FWD, Info->FcbInfo, Data1, Data2);\n      ret = 1;\n   }\n   return ret;\n}\n\n/*\n * Issue the backward callback of the 'Info' link\n * Return value: 1 if OK, 0 if not operative.\n */\nint a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2)\n{\n   int ret = 0;\n\n   if (Info->Flags & (CCC_Ended + CCC_Aborted)) {\n      /* CCC is not operative */\n   } else if (Info->Bcb) {\n      /* flag the caller */\n      if (Op == OpEnd)\n         Info->Flags |= CCC_Ended;\n      else if (Op == OpAbort)\n         Info->Flags |= CCC_Aborted;\n\n      Info->Bcb(Op, Info->BcbBranch, BCK, Info->BcbInfo, Data1, Data2);\n      ret = 1;\n   }\n   return ret;\n}\n\n/*\n * Issue the backward callback of the 'Info' link and then the\n * forward callback (used for OpAbort and OpStop).\n * Return value: 1 if OK, 0 if not operative.\n */\nint a_Chain_bfcb(int Op, ChainLink *Info, void *Data1, void *Data2)\n{\n   int ret;\n\n   ret = a_Chain_bcb(Op, Info, Data1, Data2);\n   if (ret == 1) {\n      /* we need to clear the flag to reuse this 'Info' ChainLink */\n      if (Op == OpEnd)\n         Info->Flags &= ~CCC_Ended;\n      else if (Op == OpAbort)\n         Info->Flags &= ~CCC_Aborted;\n\n      ret = a_Chain_fcb(Op, Info, Data1, Data2);\n   }\n   return ret;\n}\n\n\n/*\n * Allocate and initialize a new DataBuf structure\n */\nDataBuf *a_Chain_dbuf_new(void *buf, int size, int code)\n{\n   DataBuf *dbuf = dNew(DataBuf, 1);\n   dbuf->Buf = buf;\n   dbuf->Size = size;\n   dbuf->Code = code;\n   return dbuf;\n}\n\n/*\n * Check whether the CCC is operative.\n * Also used to hook debug information.\n *\n * Return value: 1 if ready to use, 0 if not operative.\n */\nint a_Chain_check(char *FuncStr, int Op, int Branch, int Dir,\n                  ChainLink *Info)\n{\n   int ret = 0;\n\n   /* Show status information */\n   Chain_debug_msg(FuncStr, Op, Branch, Dir, Info);\n\n   if (Info->Flags & (CCC_Ended + CCC_Aborted)) {\n      /* CCC is not operative */\n      MSG_WARN(\"CCC: call on already finished chain. Flags=%s%s\\n\",\n               Info->Flags & CCC_Ended ? \"CCC_Ended \" : \"\",\n               Info->Flags & CCC_Aborted ? \"CCC_Aborted\" : \"\");\n   } else {\n      ret = 1;\n   }\n   return ret;\n}\n\n"
  },
  {
    "path": "src/chain.h",
    "content": "#ifndef __CHAIN_H__\n#define __CHAIN_H__\n\n/*\n * Concomitant control chain (CCC)\n * Theory and code by Jorge Arellano Cid <jcid@dillo.org>\n */\n\n\n/*\n * Supported CCC operations\n */\n#define OpStart  1\n#define OpSend   2\n#define OpStop   3\n#define OpEnd    4\n#define OpAbort  5\n\n/*\n * CCC flags\n */\n#define CCC_Stopped     (1 << 0)\n#define CCC_Ended       (1 << 1)\n#define CCC_Aborted     (1 << 2)\n\n/*\n * Linking direction\n */\n#define FWD 1\n#define BCK 2\n\ntypedef struct ChainLink ChainLink;\ntypedef void (*ChainFunction_t)(int Op, int Branch, int Dir, ChainLink *Info,\n                                void *Data1, void *Data2);\n\n/* This is the main data structure for CCC nodes */\nstruct ChainLink {\n   void *LocalKey;\n\n   int Flags;\n\n   ChainLink *FcbInfo;\n   ChainFunction_t Fcb;\n   int FcbBranch;\n\n   ChainLink *BcbInfo;\n   ChainFunction_t Bcb;\n   int BcbBranch;\n};\n\n/* A convenience data structure for passing data chunks between nodes */\ntypedef struct {\n   char *Buf;\n   int Size;\n   int Code;\n} DataBuf;\n\n\n\n/*\n * Function prototypes\n */\nChainLink *a_Chain_new(void);\nChainLink *a_Chain_link_new(ChainLink *AInfo, ChainFunction_t AFunc,\n                            int Direction, ChainFunction_t BFunc,\n                            int AtoB_branch, int BtoA_branch);\nvoid a_Chain_unlink(ChainLink *Info, int Direction);\nint a_Chain_fcb(int Op, ChainLink *Info, void *Data1, void *Data2);\nint a_Chain_bcb(int Op, ChainLink *Info, void *Data1, void *Data2);\nint a_Chain_bfcb(int Op, ChainLink *Info, void *Data1, void *Data2);\nint a_Chain_check(char *FuncStr, int Op, int Branch, int Dir,\n                  ChainLink *Info);\n\nDataBuf *a_Chain_dbuf_new(void *buf, int size, int code);\n\n#endif /* __CHAIN_H__ */\n"
  },
  {
    "path": "src/chg",
    "content": "#!/bin/sh\n#\n# Shell script for name changing source code\n#\n\nif [ ! $# = 3 ]; then\n   echo \"Usage: chg <source> <old_word> <new_word>\"\n   echo \"       (this script changes <source> directly)\"\n   exit 1\nfi\n\nif [ ! -r $1 ]; then\n   echo \"source file ->$1<- doesn't exist...\"\n   exit 1\nfi\n\nif [ ! -r $1.BAK ]; then\n   echo \"creating backup file: $1.BAK\"\n   cp $1 $1.BAK\nfi\n\nsed \"s/$2/$3/g\" $1 > out\n#sed s/$2/$3/ $1 > out\nrm $1\nmv out $1\necho \"done!\"\n\n\n"
  },
  {
    "path": "src/colors.c",
    "content": "/*\n * File: colors.c\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <string.h>\n#include <stdlib.h>\n#include <ctype.h>\n#include \"colors.h\"\n\n#include \"msg.h\"\n\n/*\n * If EXTENDED_COLOR is defined, the extended set of named colors is supported.\n * These colors're not standard but they're supported in most browsers.\n * NOTE: The colors MUST be in alphabetical order and lower case because the\n *       code uses a binary search.\n */\n\n#define EXTENDED_COLOR\n\nstatic const struct key {\n   char *key;\n   int32_t val;\n} color_keyword [] = {\n#ifdef EXTENDED_COLOR\n   { \"aliceblue\", 0xf0f8ff},\n   { \"antiquewhite\", 0xfaebd7},\n#endif\n   { \"aqua\", 0x00ffff},\n#ifdef EXTENDED_COLOR\n   { \"aquamarine\", 0x7fffd4},\n   { \"azure\", 0xf0ffff},\n   { \"beige\", 0xf5f5dc},\n   { \"bisque\", 0xffe4c4},\n#endif\n   { \"black\", 0x000000},\n#ifdef EXTENDED_COLOR\n   { \"blanchedalmond\", 0xffebcd},\n#endif\n   {\"blue\", 0x0000ff},\n#ifdef EXTENDED_COLOR\n   { \"blueviolet\", 0x8a2be2},\n   { \"brown\", 0xa52a2a},\n   { \"burlywood\", 0xdeb887},\n   { \"cadetblue\", 0x5f9ea0},\n   { \"chartreuse\", 0x7fff00},\n   { \"chocolate\", 0xd2691e},\n   { \"coral\", 0xff7f50},\n   { \"cornflowerblue\", 0x6495ed},\n   { \"cornsilk\", 0xfff8dc},\n   { \"crimson\", 0xdc1436},\n   { \"cyan\", 0x00ffff},\n   { \"darkblue\", 0x00008b},\n   { \"darkcyan\", 0x008b8b},\n   { \"darkgoldenrod\", 0xb8860b},\n   { \"darkgray\", 0xa9a9a9},\n   { \"darkgreen\", 0x006400},\n   { \"darkgrey\", 0xa9a9a9},\n   { \"darkkhaki\", 0xbdb76b},\n   { \"darkmagenta\", 0x8b008b},\n   { \"darkolivegreen\", 0x556b2f},\n   { \"darkorange\", 0xff8c00},\n   { \"darkorchid\", 0x9932cc},\n   { \"darkred\", 0x8b0000},\n   { \"darksalmon\", 0xe9967a},\n   { \"darkseagreen\", 0x8fbc8f},\n   { \"darkslateblue\", 0x483d8b},\n   { \"darkslategray\", 0x2f4f4f},\n   { \"darkslategrey\", 0x2f4f4f},\n   { \"darkturquoise\", 0x00ced1},\n   { \"darkviolet\", 0x9400d3},\n   { \"deeppink\", 0xff1493},\n   { \"deepskyblue\", 0x00bfff},\n   { \"dimgray\", 0x696969},\n   { \"dimgrey\", 0x696969},\n   { \"dodgerblue\", 0x1e90ff},\n   { \"firebrick\", 0xb22222},\n   { \"floralwhite\", 0xfffaf0},\n   { \"forestgreen\", 0x228b22},\n#endif\n   { \"fuchsia\", 0xff00ff},\n#ifdef EXTENDED_COLOR\n   { \"gainsboro\", 0xdcdcdc},\n   { \"ghostwhite\", 0xf8f8ff},\n   { \"gold\", 0xffd700},\n   { \"goldenrod\", 0xdaa520},\n#endif\n   { \"gray\", 0x808080},\n   { \"green\", 0x008000},\n#ifdef EXTENDED_COLOR\n   { \"greenyellow\", 0xadff2f},\n   { \"grey\", 0x808080},\n   { \"honeydew\", 0xf0fff0},\n   { \"hotpink\", 0xff69b4},\n   { \"indianred\", 0xcd5c5c},\n   { \"indigo\", 0x4b0082},\n   { \"ivory\", 0xfffff0},\n   { \"khaki\", 0xf0e68c},\n   { \"lavender\", 0xe6e6fa},\n   { \"lavenderblush\", 0xfff0f5},\n   { \"lawngreen\", 0x7cfc00},\n   { \"lemonchiffon\", 0xfffacd},\n   { \"lightblue\", 0xadd8e6},\n   { \"lightcoral\", 0xf08080},\n   { \"lightcyan\", 0xe0ffff},\n   { \"lightgoldenrodyellow\", 0xfafad2},\n   { \"lightgray\", 0xd3d3d3},\n   { \"lightgreen\", 0x90ee90},\n   { \"lightgrey\", 0xd3d3d3},\n   { \"lightpink\", 0xffb6c1},\n   { \"lightsalmon\", 0xffa07a},\n   { \"lightseagreen\", 0x20b2aa},\n   { \"lightskyblue\", 0x87cefa},\n   { \"lightslategray\", 0x778899},\n   { \"lightslategrey\", 0x778899},\n   { \"lightsteelblue\", 0xb0c4de},\n   { \"lightyellow\", 0xffffe0},\n#endif\n   { \"lime\", 0x00ff00},\n#ifdef EXTENDED_COLOR\n   { \"limegreen\", 0x32cd32},\n   { \"linen\", 0xfaf0e6},\n   { \"magenta\", 0xff00ff},\n#endif\n   { \"maroon\", 0x800000},\n#ifdef EXTENDED_COLOR\n   { \"mediumaquamarine\", 0x66cdaa},\n   { \"mediumblue\", 0x0000cd},\n   { \"mediumorchid\", 0xba55d3},\n   { \"mediumpurple\", 0x9370db},\n   { \"mediumseagreen\", 0x3cb371},\n   { \"mediumslateblue\", 0x7b68ee},\n   { \"mediumspringgreen\", 0x00fa9a},\n   { \"mediumturquoise\", 0x48d1cc},\n   { \"mediumvioletred\", 0xc71585},\n   { \"midnightblue\", 0x191970},\n   { \"mintcream\", 0xf5fffa},\n   { \"mistyrose\", 0xffe4e1},\n   { \"moccasin\", 0xffe4b5},\n   { \"navajowhite\", 0xffdead},\n#endif\n   { \"navy\", 0x000080},\n#ifdef EXTENDED_COLOR\n   { \"oldlace\", 0xfdf5e6},\n#endif\n   { \"olive\", 0x808000},\n#ifdef EXTENDED_COLOR\n   { \"olivedrab\", 0x6b8e23},\n   { \"orange\", 0xffa500},\n   { \"orangered\", 0xff4500},\n   { \"orchid\", 0xda70d6},\n   { \"palegoldenrod\", 0xeee8aa},\n   { \"palegreen\", 0x98fb98},\n   { \"paleturquoise\", 0xafeeee},\n   { \"palevioletred\", 0xdb7093},\n   { \"papayawhip\", 0xffefd5},\n   { \"peachpuff\", 0xffdab9},\n   { \"peru\", 0xcd853f},\n   { \"pink\", 0xffc0cb},\n   { \"plum\", 0xdda0dd},\n   { \"powderblue\", 0xb0e0e6},\n#endif\n   { \"purple\", 0x800080},\n   { \"red\", 0xff0000},\n#ifdef EXTENDED_COLOR\n   { \"rosybrown\", 0xbc8f8f},\n   { \"royalblue\", 0x4169e1},\n   { \"saddlebrown\", 0x8b4513},\n   { \"salmon\", 0xfa8072},\n   { \"sandybrown\", 0xf4a460},\n   { \"seagreen\", 0x2e8b57},\n   { \"seashell\", 0xfff5ee},\n   { \"sienna\", 0xa0522d},\n#endif\n   { \"silver\", 0xc0c0c0},\n#ifdef EXTENDED_COLOR\n   { \"skyblue\", 0x87ceeb},\n   { \"slateblue\", 0x6a5acd},\n   { \"slategray\", 0x708090},\n   { \"slategrey\", 0x708090},\n   { \"snow\", 0xfffafa},\n   { \"springgreen\", 0x00ff7f},\n   { \"steelblue\", 0x4682b4},\n   { \"tan\", 0xd2b48c},\n#endif\n   { \"teal\", 0x008080},\n#ifdef EXTENDED_COLOR\n   { \"thistle\", 0xd8bfd8},\n   { \"tomato\", 0xff6347},\n   { \"turquoise\", 0x40e0d0},\n   { \"violet\", 0xee82ee},\n   { \"wheat\", 0xf5deb3},\n#endif\n   { \"white\", 0xffffff},\n#ifdef EXTENDED_COLOR\n   { \"whitesmoke\", 0xf5f5f5},\n#endif\n   { \"yellow\", 0xffff00},\n#ifdef EXTENDED_COLOR\n   { \"yellowgreen\", 0x9acd32},\n#endif\n};\n\n#define NCOLORS   (sizeof(color_keyword) / sizeof(color_keyword[0]))\n\n/*\n * Parse a color in hex (RRGGBB) or (RGB)\n *\n * Return Value:\n *   parsed color if successful (err = 0),\n *   default_color on error (err = 1).\n */\nstatic int32_t Color_parse_hex (const char *s, int32_t default_color, int *err)\n{\n  int32_t ret_color;\n  char *tail;\n\n  *err = 1;\n  ret_color = strtol(s, &tail, 16);\n  if (tail - s == 6)\n     *err = 0;\n  else if (tail - s == 3) {       /* #RGB as allowed by CSS */\n     *err = 0;\n         ret_color = ((ret_color & 0xf00) << 12) | ((ret_color & 0xf00) << 8) |\n                     ((ret_color & 0x0f0) << 8)  | ((ret_color & 0x0f0) << 4) |\n                     ((ret_color & 0x00f) << 4)  | ((ret_color & 0x00f) << 0);\n  } else\n     ret_color = default_color;\n\n  return ret_color;\n}\n\n/*\n * Parse a color string.\n *\n * - If the string begins with # or with 0x, return the color number\n *   (with 'RGB' expanded to 'RRGGBB').\n * - Else search the set of named colors.\n * - As a last resort, treat it as bare hex as in the first case.\n *\n * Return Value:\n *    Parsed color if successful,\n *    default_color on error.\n *\n * \"err\" argument:\n *    0 if a color beginning with '#' is successfully parsed\n *      or the color is a recognized word.\n *    1 if the color is bare hex or can't be parsed at all.\n *    2 if a color beginning with 0[xX] is successfully parsed.\n */\nint32_t a_Color_parse (const char *str, int32_t default_color, int *err)\n{\n   const char *cp;\n   int32_t ret_color;\n   int ret, low, mid, high, st = 1;\n\n   /* skip leading spaces */\n   for (cp = str; dIsspace(*cp); cp++);\n\n   ret_color = default_color;\n   if (*cp == '#') {\n      ret_color = Color_parse_hex(cp + 1, default_color, &st);\n\n   } else if (*cp == '0' && (cp[1] == 'x' || cp[1] == 'X') ) {\n      ret_color = Color_parse_hex(cp + 2, default_color, &st);\n      if (!st)\n         st = 2;\n   } else {\n      /* Binary search */\n      low = 0;\n      high = NCOLORS - 1;\n      while (low <= high) {\n         mid = (low + high) / 2;\n         if ((ret = dStrAsciiCasecmp(cp, color_keyword[mid].key)) < 0)\n            high = mid - 1;\n         else if (ret > 0)\n            low = mid + 1;\n         else {\n            ret_color = color_keyword[mid].val;\n            st = 0;\n            break;\n         }\n      }\n\n      if (low > high) {\n         /* try for RRGGBB lacking the leading '#' */\n         ret_color = Color_parse_hex(cp, default_color, &st);\n         st = 1;\n      }\n   }\n\n   _MSG(\"color string: %s\\n\", str);\n   _MSG(\"color :  %X\\n\", ret_color);\n\n   *err = st;\n   return ret_color;\n}\n\n#if 0\n/*\n * Return a \"distance\" measure (between [0, 10])\n */\nstatic int Color_distance(long c1, long c2)\n{\n   return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) +\n           labs(((c1 & 0x00ff00) - (c2 & 0x00ff00)) >> 8) +\n           labs(((c1 & 0xff0000) - (c2 & 0xff0000)) >> 16)) / 75;\n}\n#endif\n\n/*\n * Return: [0-3]\n */\nstatic int Color_distance2(long c1, long c2)\n{\n   return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000060) +\n          (labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x006000) +\n          (labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x600000);\n}\n\n/*\n * Return: [0-3] (requires less contrast than distance2)\n */\nstatic int Color_distance3(long c1, long c2)\n{\n   return (labs((c1 & 0x0000ff) - (c2 & 0x0000ff)) >= 0x000040) +\n          (labs((c1 & 0x00ff00) - (c2 & 0x00ff00)) >= 0x004000) +\n          (labs((c1 & 0xff0000) - (c2 & 0xff0000)) >= 0x400000);\n}\n\n/*\n * Return a suitable \"visited link\" color\n * Return value:\n *   if candidate has good contrast with C_txt, C_lnk and C_bg  -> candidate\n *   else another color (from the internal list)\n */\nint32_t a_Color_vc(int32_t candidate,\n                   int32_t C_txt, int32_t C_lnk, int32_t C_bg)\n{\n                    /* candidate purple    darkcyan  darkmagenta olive   */\n  static int32_t v[] = {0x000000, 0x800080, 0x008b8b, 0x8b008b, 0x808000,\n                    /* darkred   coral     black                        */\n                       0x8b0000, 0xff7f50, 0x000000};\n  int v_size = sizeof(v) / sizeof(v[0]);\n  int i, max_i, score, max_score, d_bg, d_txt, d_lnk;\n\n\n  /* set candidate in the list */\n  v[0] = candidate;\n\n  /* Try to get good overall and individual contrast */\n  max_i = max_score = 0;\n  for (i = 0; i < v_size; ++i) {\n     _MSG(\"a_Color_vc: [%d]%.6x: %d %d %d\\n\", i, v[i],\n         Color_distance2(C_txt, v[i]),\n         Color_distance2(C_lnk, v[i]),\n         Color_distance2(C_bg, v[i]));\n\n     /* Tuned with: slashdot.org, paulgraham.com, newsforge.com,\n      *             linuxjournal.com\n      */\n     d_txt = Color_distance2(C_txt, v[i]);\n     d_lnk = Color_distance2(C_lnk, v[i]);\n     d_bg  = Color_distance2(C_bg, v[i]);\n     score = (d_bg >= 2 ? 4 : 2 * d_bg) +\n             (d_txt + d_lnk >= 2 ? 2 : d_txt + d_lnk) +\n             (Color_distance3(C_lnk, v[i]) >= 1 ? 1 : 0);\n     if (score >= 7) {\n        /* enough distance, use this color */\n        max_i = i;\n        break;\n     } else if (score > max_score) {\n        /* keep track of the best candidate so far */\n        max_score = score;\n        max_i = i;\n     }\n  }\n  return v[max_i];\n}\n"
  },
  {
    "path": "src/colors.h",
    "content": "#ifndef __COLORS_H__\n#define __COLORS_H__\n\n#include \"config.h\"\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nint32_t a_Color_parse (const char *str, int32_t default_color, int *err);\nint32_t a_Color_vc(int32_t candidate, int32_t c1, int32_t c2, int32_t c3);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __COLORS_H__ */\n"
  },
  {
    "path": "src/cookies.c",
    "content": "/*\n * File: cookies.c\n *\n * Copyright 2001 Lars Clausen   <lrclause@cs.uiuc.edu>\n *                Jrgen Viksell <jorgen.viksell@telia.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/* Handling of cookies takes place here. */\n\n#include \"msg.h\"\n\n#ifdef DISABLE_COOKIES\n\n/*\n * Initialize the cookies module\n */\nvoid a_Cookies_init(void)\n{\n   MSG(\"Cookies: absolutely disabled at compilation time.\\n\");\n}\n\n#else\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/file.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <ctype.h>\n#include <errno.h>\n\n#include \"IO/Url.h\"\n#include \"list.h\"\n#include \"cookies.h\"\n#include \"capi.h\"\n#include \"../dpip/dpip.h\"\n\n\n/* The maximum length of a line in the cookie file */\n#define LINE_MAXLEN 4096\n\ntypedef enum {\n   COOKIE_ACCEPT,\n   COOKIE_ACCEPT_SESSION,\n   COOKIE_DENY\n} CookieControlAction;\n\ntypedef struct {\n   CookieControlAction action;\n   char *domain;\n} CookieControl;\n\n/* Variables for access control */\nstatic CookieControl *ccontrol = NULL;\nstatic int num_ccontrol = 0;\nstatic int num_ccontrol_max = 1;\nstatic CookieControlAction default_action = COOKIE_DENY;\n\nstatic bool_t disabled;\n\nstatic FILE *Cookies_fopen(const char *file, char *init_str);\nstatic CookieControlAction Cookies_control_check(const DilloUrl *url);\nstatic CookieControlAction Cookies_control_check_domain(const char *domain);\nstatic int Cookie_control_init(void);\n\n/*\n * Return a file pointer. If the file doesn't exist, try to create it,\n * with the optional 'init_str' as its content.\n */\nstatic FILE *Cookies_fopen(const char *filename, char *init_str)\n{\n   FILE *F_in;\n   int fd, rc;\n\n   if ((F_in = fopen(filename, \"r\")) == NULL) {\n      /* Create the file */\n      fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);\n      if (fd != -1) {\n         if (init_str) {\n            rc = write(fd, init_str, strlen(init_str));\n            if (rc == -1) {\n               MSG(\"Cookies: Could not write initial string to file %s: %s\\n\",\n                   filename, dStrerror(errno));\n            }\n         }\n         dClose(fd);\n\n         MSG(\"Cookies: Created file: %s\\n\", filename);\n         F_in = fopen(filename, \"r\");\n      } else {\n         MSG(\"Cookies: Could not create file: %s!\\n\", filename);\n      }\n   }\n\n   if (F_in) {\n      /* set close on exec */\n      fcntl(fileno(F_in), F_SETFD, FD_CLOEXEC | fcntl(fileno(F_in), F_GETFD));\n   }\n\n   return F_in;\n}\n\n/*\n * Initialize the cookies module\n * (The 'disabled' variable is writable only within a_Cookies_init)\n */\nvoid a_Cookies_init(void)\n{\n   /* Default setting */\n   disabled = TRUE;\n\n   /* Read and parse the cookie control file (cookiesrc) */\n   if (Cookie_control_init() != 0) {\n      MSG(\"Disabling cookies.\\n\");\n      return;\n   }\n\n   MSG(\"Enabling cookies as from cookiesrc...\\n\");\n   disabled = FALSE;\n}\n\n/*\n * Flush cookies to disk and free all the memory allocated.\n */\nvoid a_Cookies_freeall()\n{\n}\n\n/*\n * Set the value corresponding to the cookie string\n */\nvoid a_Cookies_set(Dlist *cookie_strings, const DilloUrl *set_url,\n                   const char *date)\n{\n   CookieControlAction action;\n   char *cmd, *cookie_string, *dpip_tag;\n   const char *path;\n   int i;\n\n   if (disabled)\n      return;\n\n   action = Cookies_control_check(set_url);\n   if (action == COOKIE_DENY) {\n      _MSG(\"Cookies: denied SET for %s\\n\", URL_HOST_(set_url));\n      return;\n   }\n\n   for (i = 0; (cookie_string = dList_nth_data(cookie_strings, i)); ++i) {\n      path = URL_PATH_(set_url);\n      if (date)\n         cmd = a_Dpip_build_cmd(\"cmd=%s cookie=%s host=%s path=%s date=%s\",\n                                \"set_cookie\", cookie_string,\n                                URL_HOST_(set_url), path ? path : \"/\", date);\n      else\n         cmd = a_Dpip_build_cmd(\"cmd=%s cookie=%s host=%s path=%s\",\n                                \"set_cookie\", cookie_string,\n                                URL_HOST_(set_url), path ? path : \"/\");\n\n      _MSG(\"Cookies.c: a_Cookies_set \\n\\t \\\"%s\\\" \\n\",cmd );\n      /* This call is commented because it doesn't guarantee the order\n       * in which cookies are set and got. (It may deadlock too) */\n      //a_Capi_dpi_send_cmd(NULL, NULL, cmd, \"cookies\", 1);\n\n      dpip_tag = a_Dpi_send_blocking_cmd(\"cookies\", cmd);\n      _MSG(\"a_Cookies_set: dpip_tag = {%s}\\n\", dpip_tag);\n      dFree(dpip_tag);\n      dFree(cmd);\n   }\n}\n\n/*\n * Return a string containing cookie data for an HTTP query.\n */\nchar *a_Cookies_get_query(const DilloUrl *query_url, const DilloUrl *requester)\n{\n   char *cmd, *dpip_tag, *query;\n   const char *path;\n   CookieControlAction action;\n\n   if (disabled)\n      return dStrdup(\"\");\n\n   action = Cookies_control_check(query_url);\n   if (action == COOKIE_DENY) {\n      _MSG(\"Cookies: denied GET for %s\\n\", URL_HOST_(query_url));\n      return dStrdup(\"\");\n   }\n\n   if (requester == NULL) {\n      /* request made by user */\n   } else if (!a_Url_same_organization(query_url, requester)) {\n      MSG(\"Cookies: not sent for request by '%s' for '%s'\\n\",\n          URL_HOST(requester), URL_HOST(query_url));\n      return dStrdup(\"\");\n   }\n\n   path = URL_PATH_(query_url);\n\n   cmd = a_Dpip_build_cmd(\"cmd=%s scheme=%s host=%s path=%s\",\n                          \"get_cookie\", URL_SCHEME(query_url),\n                         URL_HOST(query_url), path ? path : \"/\");\n\n   /* Get the answer from cookies.dpi */\n   _MSG(\"cookies.c: a_Dpi_send_blocking_cmd cmd = {%s}\\n\", cmd);\n   dpip_tag = a_Dpi_send_blocking_cmd(\"cookies\", cmd);\n   _MSG(\"cookies.c: after a_Dpi_send_blocking_cmd resp={%s}\\n\", dpip_tag);\n   dFree(cmd);\n\n   if (dpip_tag != NULL) {\n      query = a_Dpip_get_attr(dpip_tag, \"cookie\");\n      dFree(dpip_tag);\n   } else {\n      query = dStrdup(\"\");\n   }\n   return query;\n}\n\n/* -------------------------------------------------------------\n *                    Access control routines\n * ------------------------------------------------------------- */\n\n\n/*\n * Get the cookie control rules (from cookiesrc).\n * Return value:\n *   0 = Parsed OK, with cookies enabled\n *   1 = Parsed OK, with cookies disabled\n *   2 = Can't open the control file\n */\nstatic int Cookie_control_init(void)\n{\n   CookieControl cc;\n   FILE *stream;\n   char *filename, *rc;\n   char line[LINE_MAXLEN];\n   char domain[LINE_MAXLEN];\n   char rule[LINE_MAXLEN];\n   bool_t enabled = FALSE;\n\n   /* Get a file pointer */\n   filename = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/cookiesrc\", NULL);\n   stream = Cookies_fopen(filename, \"DEFAULT DENY\\n\");\n   dFree(filename);\n\n   if (!stream)\n      return 2;\n\n   /* Get all lines in the file */\n   while (!feof(stream)) {\n      line[0] = '\\0';\n      rc = fgets(line, LINE_MAXLEN, stream);\n      if (!rc && ferror(stream)) {\n         MSG(\"Cookies1: Error while reading rule from cookiesrc: %s\\n\",\n             dStrerror(errno));\n         fclose(stream);\n         return 2; /* bail out */\n      }\n\n      /* Remove leading and trailing whitespaces */\n      dStrstrip(line);\n\n      if (line[0] != '\\0' && line[0] != '#') {\n         int i = 0, j = 0;\n\n         /* Get the domain */\n         while (line[i] != '\\0' && !dIsspace(line[i]))\n            domain[j++] = line[i++];\n         domain[j] = '\\0';\n\n         /* Skip past whitespaces */\n         while (dIsspace(line[i]))\n            i++;\n\n         /* Get the rule */\n         j = 0;\n         while (line[i] != '\\0' && !dIsspace(line[i]))\n            rule[j++] = line[i++];\n         rule[j] = '\\0';\n\n         if (dStrAsciiCasecmp(rule, \"ACCEPT\") == 0)\n            cc.action = COOKIE_ACCEPT;\n         else if (dStrAsciiCasecmp(rule, \"ACCEPT_SESSION\") == 0)\n            cc.action = COOKIE_ACCEPT_SESSION;\n         else if (dStrAsciiCasecmp(rule, \"DENY\") == 0)\n            cc.action = COOKIE_DENY;\n         else {\n            MSG(\"Cookies: rule '%s' for domain '%s' is not recognised.\\n\",\n                rule, domain);\n            continue;\n         }\n\n         cc.domain = dStrdup(domain);\n         if (dStrAsciiCasecmp(cc.domain, \"DEFAULT\") == 0) {\n            /* Set the default action */\n            default_action = cc.action;\n            dFree(cc.domain);\n         } else {\n            int i;\n            uint_t len = strlen(cc.domain);\n\n            /* Insert into list such that longest rules come first. */\n            a_List_add(ccontrol, num_ccontrol, num_ccontrol_max);\n            for (i = num_ccontrol++;\n                 i > 0 && (len > strlen(ccontrol[i-1].domain));\n                 i--) {\n               ccontrol[i] = ccontrol[i-1];\n            }\n            ccontrol[i] = cc;\n         }\n\n         if (cc.action != COOKIE_DENY)\n            enabled = TRUE;\n      }\n   }\n\n   fclose(stream);\n\n   return (enabled ? 0 : 1);\n}\n\n/*\n * Check the rules for an appropriate action for this domain.\n * The rules are ordered by domain length, with longest first, so the\n * first match is the most specific.\n */\nstatic CookieControlAction Cookies_control_check_domain(const char *domain)\n{\n   int i, diff;\n\n   for (i = 0; i < num_ccontrol; i++) {\n      if (ccontrol[i].domain[0] == '.') {\n         diff = strlen(domain) - strlen(ccontrol[i].domain);\n         if (diff >= 0) {\n            if (dStrAsciiCasecmp(domain + diff, ccontrol[i].domain) != 0)\n               continue;\n         } else {\n            continue;\n         }\n      } else {\n         if (dStrAsciiCasecmp(domain, ccontrol[i].domain) != 0)\n            continue;\n      }\n\n      /* If we got here we have a match */\n      return( ccontrol[i].action );\n   }\n\n   return default_action;\n}\n\n/*\n * Same as the above except it takes an URL\n */\nstatic CookieControlAction Cookies_control_check(const DilloUrl *url)\n{\n   return Cookies_control_check_domain(URL_HOST(url));\n}\n\n#endif /* !DISABLE_COOKIES */\n"
  },
  {
    "path": "src/cookies.h",
    "content": "#ifndef __COOKIES_H__\n#define __COOKIES_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nvoid  a_Cookies_init( void );\n\n#ifdef DISABLE_COOKIES\n# define a_Cookies_get_query(url, requester)  dStrdup(\"\")\n# define a_Cookies_set()     ;\n# define a_Cookies_freeall() ;\n#else\n  char *a_Cookies_get_query(const DilloUrl *query_url,\n                            const DilloUrl *requester);\n  void  a_Cookies_set(Dlist *cookie_string, const DilloUrl *set_url,\n                      const char *server_date);\n  void  a_Cookies_freeall( void );\n#endif\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* !__COOKIES_H__ */\n"
  },
  {
    "path": "src/css.cc",
    "content": "/*\n * File: css.cc\n *\n * Copyright 2008-2014 Johannes Hofmann <Johannes.Hofmann@gmx.de>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <stdio.h>\n#include \"../dlib/dlib.h\"\n#include \"msg.h\"\n#include \"html_common.hh\"\n#include \"css.hh\"\n\nusing namespace dw::core::style;\n\nvoid CssProperty::print () {\n   fprintf (stderr, \"%s - %d\\n\",\n            CssParser::propertyNameString((CssPropertyName)name),\n            (int)value.intVal);\n}\n\nCssPropertyList::CssPropertyList (const CssPropertyList &p, bool deep) :\n   lout::misc::SimpleVector <CssProperty> (p)\n{\n   refCount = 0;\n   safe = p.safe;\n   if (deep) {\n      for (int i = 0; i < size (); i++) {\n         CssProperty *p = getRef(i);\n         switch (p->type) {\n            case CSS_TYPE_STRING:\n            case CSS_TYPE_SYMBOL:\n               p->value.strVal = dStrdup (p->value.strVal);\n               break;\n            default:\n               break;\n         }\n      }\n      ownerOfStrings = true;\n   } else {\n      ownerOfStrings = false;\n   }\n}\n\nCssPropertyList::~CssPropertyList () {\n   if (ownerOfStrings)\n      for (int i = 0; i < size (); i++)\n         getRef (i)->free ();\n}\n\n/**\n * \\brief Set property to a given name and type.\n */\nvoid CssPropertyList::set (CssPropertyName name, CssValueType type,\n                           CssPropertyValue value) {\n   CssProperty *prop;\n\n   if (name == CSS_PROPERTY_DISPLAY || name == CSS_PROPERTY_BACKGROUND_IMAGE)\n      safe = false;\n\n   for (int i = 0; i < size (); i++) {\n      prop = getRef (i);\n\n      if (prop->name == name) {\n         if (ownerOfStrings)\n            prop->free ();\n         prop->type = type;\n         prop->value = value;\n         return;\n      }\n   }\n\n   increase ();\n   prop = getRef (size () - 1);\n   prop->name = name;\n   prop->type = type;\n   prop->value = value;\n}\n\n/**\n * \\brief Merge properties into argument property list.\n */\nvoid CssPropertyList::apply (CssPropertyList *props) {\n   for (int i = 0; i < size (); i++) {\n      CssPropertyValue value = getRef (i)->value;\n\n      if (props->ownerOfStrings &&\n          (getRef (i)->type == CSS_TYPE_STRING ||\n           getRef (i)->type == CSS_TYPE_SYMBOL))\n         value.strVal = strdup(value.strVal);\n\n      props->set ((CssPropertyName) getRef (i)->name,\n                  (CssValueType) getRef (i)->type,\n                  value);\n   }\n}\n\nvoid CssPropertyList::print () {\n   for (int i = 0; i < size (); i++)\n      getRef (i)->print ();\n}\n\nCssSelector::CssSelector () {\n   struct CombinatorAndSelector *cs;\n\n   refCount = 0;\n   matchCacheOffset = -1;\n   selectorList.increase ();\n   cs = selectorList.getRef (selectorList.size () - 1);\n\n   cs->combinator = COMB_NONE;\n   cs->selector = new CssSimpleSelector ();\n}\n\nCssSelector::~CssSelector () {\n   for (int i = selectorList.size () - 1; i >= 0; i--)\n      delete selectorList.getRef (i)->selector;\n}\n\n/**\n * \\brief Return whether selector matches at a given node in the document tree.\n */\nbool CssSelector::match (Doctree *docTree, const DoctreeNode *node,\n                         int i, Combinator comb, MatchCache *matchCache) {\n   int *matchCacheEntry;\n   assert (node);\n\n   if (i < 0)\n      return true;\n\n   struct CombinatorAndSelector *cs = selectorList.getRef (i);\n   CssSimpleSelector *sel = cs->selector;\n\n   switch (comb) {\n      case COMB_NONE:\n         break;\n      case COMB_CHILD:\n         node = docTree->parent (node);\n         break;\n      case COMB_ADJACENT_SIBLING:\n         node = docTree->sibling (node);\n         break;\n      case COMB_DESCENDANT:\n         node = docTree->parent (node);\n         matchCacheEntry = matchCache->getRef(matchCacheOffset + i);\n\n         for (const DoctreeNode *n = node;\n              n && n->num > *matchCacheEntry; n = docTree->parent (n))\n            if (sel->match (n) &&\n                match (docTree, n, i - 1, cs->combinator, matchCache))\n               return true;\n\n         if (node) // remember that it didn't match to avoid future tests\n            *matchCacheEntry = node->num;\n\n         return false;\n         break;\n      default:\n         return false; // \\todo implement other combinators\n   }\n\n   if (!node || !sel->match (node))\n      return false;\n\n   // tail recursion should be optimized by the compiler\n   return match (docTree, node, i - 1, cs->combinator, matchCache);\n}\n\nvoid CssSelector::addSimpleSelector (Combinator c) {\n   struct CombinatorAndSelector *cs;\n\n   assert (matchCacheOffset == -1);\n   selectorList.increase ();\n   cs = selectorList.getRef (selectorList.size () - 1);\n\n   cs->combinator = c;\n   cs->selector = new CssSimpleSelector ();\n}\n\nbool CssSelector::checksPseudoClass () {\n   for (int i = 0; i < selectorList.size (); i++)\n      if (selectorList.getRef (i)->selector->getPseudoClass ())\n         return true;\n   return false;\n}\n\n/**\n * \\brief Return the specificity of the selector.\n *\n * The specificity of a CSS selector is defined in\n * http://www.w3.org/TR/CSS21/cascade.html#specificity\n */\nint CssSelector::specificity () {\n   int spec = 0;\n\n   for (int i = 0; i < selectorList.size (); i++)\n      spec += selectorList.getRef (i)->selector->specificity ();\n\n   return spec;\n}\n\nvoid CssSelector::print () {\n   for (int i = 0; i < selectorList.size (); i++) {\n      selectorList.getRef (i)->selector->print ();\n\n      if (i < selectorList.size () - 1) {\n         switch (selectorList.getRef (i + 1)->combinator) {\n            case COMB_CHILD:\n               fprintf (stderr, \"> \");\n               break;\n            case COMB_DESCENDANT:\n               fprintf (stderr, \"\\\" \\\" \");\n               break;\n            case COMB_ADJACENT_SIBLING:\n               fprintf (stderr, \"+ \");\n               break;\n            default:\n               fprintf (stderr, \"? \");\n               break;\n         }\n      }\n   }\n\n   fprintf (stderr, \"\\n\");\n}\n\nCssSimpleSelector::CssSimpleSelector () {\n   element = ELEMENT_ANY;\n   id = NULL;\n   pseudo = NULL;\n}\n\nCssSimpleSelector::~CssSimpleSelector () {\n   for (int i = 0; i < klass.size (); i++)\n      dFree (klass.get (i));\n   dFree (id);\n   dFree (pseudo);\n}\n\nvoid CssSimpleSelector::setSelect (SelectType t, const char *v) {\n   switch (t) {\n      case SELECT_CLASS:\n         klass.increase ();\n         klass.set (klass.size () - 1, dStrdup (v));\n         break;\n      case SELECT_PSEUDO_CLASS:\n         if (pseudo == NULL)\n            pseudo = dStrdup (v);\n         break;\n      case SELECT_ID:\n         if (id == NULL)\n            id = dStrdup (v);\n         break;\n      default:\n         break;\n   }\n}\n\n/**\n * \\brief Return whether simple selector matches at a given node of\n *        the document tree.\n */\nbool CssSimpleSelector::match (const DoctreeNode *n) {\n   assert (n);\n   if (element != ELEMENT_ANY && element != n->element)\n      return false;\n   if (pseudo != NULL &&\n      (n->pseudo == NULL || dStrAsciiCasecmp (pseudo, n->pseudo) != 0))\n      return false;\n   if (id != NULL && (n->id == NULL || dStrAsciiCasecmp (id, n->id) != 0))\n      return false;\n   for (int i = 0; i < klass.size (); i++) {\n      bool found = false;\n      if (n->klass != NULL) {\n         for (int j = 0; j < n->klass->size (); j++) {\n            if (dStrAsciiCasecmp (klass.get(i), n->klass->get(j)) == 0) {\n               found = true;\n               break;\n            }\n         }\n      }\n      if (! found)\n         return false;\n   }\n\n   return true;\n}\n\n/**\n * \\brief Return the specificity of the simple selector.\n *\n * The result is used in CssSelector::specificity ().\n */\nint CssSimpleSelector::specificity () {\n   int spec = 0;\n\n   if (id)\n      spec += 1 << 20;\n   spec += klass.size() << 10;\n   if (pseudo)\n      spec += 1 << 10;\n   if (element != ELEMENT_ANY)\n      spec += 1;\n\n   return spec;\n}\n\nvoid CssSimpleSelector::print () {\n   fprintf (stderr, \"Element %d, pseudo %s, id %s \",\n      element, pseudo, id);\n   fprintf (stderr, \"class \");\n   for (int i = 0; i < klass.size (); i++)\n      fprintf (stderr, \".%s\", klass.get (i));\n}\n\nCssRule::CssRule (CssSelector *selector, CssPropertyList *props, int pos) {\n   assert (selector->size () > 0);\n\n   this->selector = selector;\n   this->selector->ref ();\n   this->props = props;\n   this->props->ref ();\n   this->pos = pos;\n   spec = selector->specificity ();\n}\n\nCssRule::~CssRule () {\n   selector->unref ();\n   props->unref ();\n}\n\nvoid CssRule::apply (CssPropertyList *props, Doctree *docTree,\n                     const DoctreeNode *node, MatchCache *matchCache) const {\n   if (selector->match (docTree, node, matchCache))\n      this->props->apply (props);\n}\n\nvoid CssRule::print () {\n   selector->print ();\n   props->print ();\n}\n\n/*\n * \\brief Insert rule with increasing specificity.\n *\n * If two rules have the same specificity, the one that was added later\n * will be added behind the others.\n * This gives later added rules more weight.\n */\nvoid CssStyleSheet::RuleList::insert (CssRule *rule) {\n   increase ();\n   int i = size () - 1;\n\n   while (i > 0 && rule->specificity () < get (i - 1)->specificity ()) {\n      *getRef (i) = get (i - 1);\n      i--;\n   }\n\n   *getRef (i) = rule;\n}\n\n/**\n * \\brief Insert a rule into CssStyleSheet.\n *\n * To improve matching performance the rules are organized into\n * rule lists based on the topmost simple selector of their selector.\n */\nvoid CssStyleSheet::addRule (CssRule *rule) {\n   CssSimpleSelector *top = rule->selector->top ();\n   RuleList *ruleList = NULL;\n   lout::object::ConstString *string;\n\n   if (top->getId ()) {\n      string = new lout::object::ConstString (top->getId ());\n      ruleList = idTable.get (string);\n      if (ruleList == NULL) {\n         ruleList = new RuleList ();\n         idTable.put (string, ruleList);\n      } else {\n         delete string;\n      }\n   } else if (top->getClass () && top->getClass ()->size () > 0) {\n      string = new lout::object::ConstString (top->getClass ()->get (0));\n      ruleList = classTable.get (string);\n      if (ruleList == NULL) {\n         ruleList = new RuleList;\n         classTable.put (string, ruleList);\n      } else {\n         delete string;\n      }\n   } else if (top->getElement () >= 0 && top->getElement () < ntags) {\n      ruleList = &elementTable[top->getElement ()];\n   } else if (top->getElement () == CssSimpleSelector::ELEMENT_ANY) {\n      ruleList = &anyTable;\n   }\n\n   if (ruleList) {\n      ruleList->insert (rule);\n      if (rule->selector->getRequiredMatchCache () > requiredMatchCache)\n         requiredMatchCache = rule->selector->getRequiredMatchCache ();\n   } else {\n      assert (top->getElement () == CssSimpleSelector::ELEMENT_NONE);\n      delete rule;\n   }\n}\n\n/**\n * \\brief Apply a stylesheet to a property list.\n *\n * The properties are set as defined by the rules in the stylesheet that\n * match at the given node in the document tree.\n */\nvoid CssStyleSheet::apply (CssPropertyList *props, Doctree *docTree,\n                        const DoctreeNode *node, MatchCache *matchCache) const {\n   static const int maxLists = 32;\n   const RuleList *ruleList[maxLists];\n   int numLists = 0, index[maxLists] = {0};\n\n   if (node->id) {\n      lout::object::ConstString idString (node->id);\n\n      ruleList[numLists] = idTable.get (&idString);\n      if (ruleList[numLists])\n         numLists++;\n   }\n\n   if (node->klass) {\n      for (int i = 0; i < node->klass->size (); i++) {\n         if (i >= maxLists - 4) {\n            MSG_WARN(\"Maximum number of classes per element exceeded.\\n\");\n            break;\n         }\n\n         lout::object::ConstString classString (node->klass->get (i));\n\n         ruleList[numLists] = classTable.get (&classString);\n         if (ruleList[numLists])\n            numLists++;\n      }\n   }\n\n   ruleList[numLists] = &elementTable[node->element];\n   if (ruleList[numLists])\n      numLists++;\n\n   ruleList[numLists] = &anyTable;\n   if (ruleList[numLists])\n      numLists++;\n\n   // Apply potentially matching rules from ruleList[0-numLists] with\n   // ascending specificity.\n   // If specificity is equal, rules are applied in order of appearance.\n   //  Each ruleList is sorted already.\n   while (true) {\n      int minSpec = 1 << 30;\n      int minPos = 1 << 30;\n      int minSpecIndex = -1;\n\n      for (int i = 0; i < numLists; i++) {\n         const RuleList *rl = ruleList[i];\n\n         if (rl && rl->size () > index[i] &&\n            (rl->get(index[i])->specificity () < minSpec ||\n             (rl->get(index[i])->specificity () == minSpec &&\n              rl->get(index[i])->position () < minPos))) {\n\n            minSpec = rl->get(index[i])->specificity ();\n            minPos = rl->get(index[i])->position ();\n            minSpecIndex = i;\n         }\n      }\n\n      if (minSpecIndex >= 0) {\n         CssRule *rule = ruleList[minSpecIndex]->get (index[minSpecIndex]);\n         rule->apply(props, docTree, node, matchCache);\n         index[minSpecIndex]++;\n      } else {\n         break;\n      }\n   }\n}\n\nCssStyleSheet CssContext::userAgentSheet;\n\nCssContext::CssContext () {\n   pos = 0;\n   matchCache.setSize (userAgentSheet.getRequiredMatchCache (), -1);\n}\n\n/**\n * \\brief Apply a CSS context to a property list.\n *\n * The stylesheets in the context are applied one after the other\n * in the ordering defined by CSS 2.1.\n * Stylesheets that are applied later can overwrite properties set\n * by previous stylesheets.\n * This allows e.g. user styles to overwrite author styles.\n */\nvoid CssContext::apply (CssPropertyList *props, Doctree *docTree,\n         DoctreeNode *node,\n         CssPropertyList *tagStyle, CssPropertyList *tagStyleImportant,\n         CssPropertyList *nonCssHints) {\n\n   userAgentSheet.apply (props, docTree, node, &matchCache);\n\n   sheet[CSS_PRIMARY_USER].apply (props, docTree, node, &matchCache);\n\n   if (nonCssHints)\n        nonCssHints->apply (props);\n\n   sheet[CSS_PRIMARY_AUTHOR].apply (props, docTree, node, &matchCache);\n\n   if (tagStyle)\n        tagStyle->apply (props);\n\n   sheet[CSS_PRIMARY_AUTHOR_IMPORTANT].apply (props, docTree, node,\n                                              &matchCache);\n\n   if (tagStyleImportant)\n        tagStyleImportant->apply (props);\n\n   sheet[CSS_PRIMARY_USER_IMPORTANT].apply (props, docTree, node, &matchCache);\n}\n\nvoid CssContext::addRule (CssSelector *sel, CssPropertyList *props,\n                          CssPrimaryOrder order) {\n\n   if (props->size () > 0) {\n      CssRule *rule = new CssRule (sel, props, pos++);\n\n      if ((order == CSS_PRIMARY_AUTHOR ||\n           order == CSS_PRIMARY_AUTHOR_IMPORTANT) &&\n           !rule->isSafe ()) {\n         _MSG_WARN (\"Ignoring unsafe author style that might reveal browsing history\\n\");\n         delete rule;\n      } else {\n         rule->selector->setMatchCacheOffset(matchCache.size ());\n         if (rule->selector->getRequiredMatchCache () > matchCache.size ())\n            matchCache.setSize (rule->selector->getRequiredMatchCache (), -1);\n\n         if (order == CSS_PRIMARY_USER_AGENT) {\n            userAgentSheet.addRule (rule);\n         } else {\n            sheet[order].addRule (rule);\n         }\n      }\n   }\n}\n"
  },
  {
    "path": "src/css.hh",
    "content": "#ifndef __CSS_HH__\n#define __CSS_HH__\n\n#include \"dw/core.hh\"\n#include \"doctree.hh\"\n\n/* Origin and weight. Used only internally.*/\ntypedef enum {\n   CSS_PRIMARY_USER_AGENT,\n   CSS_PRIMARY_USER,\n   CSS_PRIMARY_AUTHOR,\n   CSS_PRIMARY_AUTHOR_IMPORTANT,\n   CSS_PRIMARY_USER_IMPORTANT,\n   CSS_PRIMARY_LAST,\n} CssPrimaryOrder;\n\ntypedef enum {\n   CSS_ORIGIN_USER_AGENT,\n   CSS_ORIGIN_USER,\n   CSS_ORIGIN_AUTHOR,\n} CssOrigin;\n\ntypedef enum {\n   CSS_TYPE_INTEGER,            /* This type is only used internally, for x-*\n                                   properties. */\n   CSS_TYPE_ENUM,               /* Value is i, if represented by\n                                   enum_symbols[i]. */\n   CSS_TYPE_MULTI_ENUM,         /* For all enum_symbols[i], 1 << i are\n                                   combined. */\n   CSS_TYPE_LENGTH_PERCENTAGE,  /* <length> or <percentage>. Represented by\n                                   CssLength. */\n   CSS_TYPE_LENGTH,             /* <length>, represented as CssLength.\n                                   Note: In some cases, CSS_TYPE_LENGTH is used\n                                   instead of CSS_TYPE_LENGTH_PERCENTAGE,\n                                   only because Dw cannot handle percentages\n                                   in this particular case (e.g.\n                                   'margin-*-width'). */\n   CSS_TYPE_SIGNED_LENGTH,      /* As CSS_TYPE_LENGTH but may be negative. */\n   CSS_TYPE_LENGTH_PERCENTAGE_NUMBER,  /* <length> or <percentage>, or <number> */\n   CSS_TYPE_AUTO,               /* Represented as CssLength of type\n                                   CSS_LENGTH_TYPE_AUTO */\n   CSS_TYPE_COLOR,              /* Represented as integer. */\n   CSS_TYPE_FONT_WEIGHT,        /* this very special and only used by\n                                   'font-weight' */\n   CSS_TYPE_STRING,             /* <string> */\n   CSS_TYPE_SYMBOL,             /* Symbols, which are directly copied (as\n                                   opposed to CSS_TYPE_ENUM and\n                                   CSS_TYPE_MULTI_ENUM). Used for\n                                   'font-family'. */\n   CSS_TYPE_URI,                /* <uri> */\n   CSS_TYPE_BACKGROUND_POSITION,\n   CSS_TYPE_UNUSED              /* Not yet used. Will itself get unused some\n                                   day. */\n} CssValueType;\n\n/*\n * Lengths are represented as int in the following way:\n *\n *    | <------   integer value   ------> |\n *\n *    +---+ - - - +---+---+- - - - - -+---+---+---+---+\n *    |          integer part             |   type    |\n *    +---+ - - - +---+---+- - - - - -+---+---+---+---+\n *    | integer part  | decimal fraction  |   type    |\n *    +---+ - - - +---+---+- - - - - -+---+---+---+---+\n *     n-1          15  14              3   2  1   0\n *\n *    | <------ fixed point value ------> |\n *\n * where type is one of the CSS_LENGTH_TYPE_* values.\n * CSS_LENGTH_TYPE_PX values are stored as\n * 29 bit signed integer, all other types as fixed point values.\n */\n\ntypedef int CssLength;\n\ntypedef enum {\n   CSS_LENGTH_TYPE_NONE,\n   CSS_LENGTH_TYPE_PX,\n   CSS_LENGTH_TYPE_MM,         /* \"cm\", \"in\", \"pt\" and \"pc\" are converted into\n                                  millimeters. */\n   CSS_LENGTH_TYPE_EM,\n   CSS_LENGTH_TYPE_EX,\n/* CSS_LENGTH_TYPE_CH, */      /* not used to remain in the 3 bits space, converted to EM by cssparser */\n   CSS_LENGTH_TYPE_PERCENTAGE,\n   CSS_LENGTH_TYPE_RELATIVE,   /* This does not exist in CSS but\n                                  is used in HTML */\n   CSS_LENGTH_TYPE_AUTO        /* This can be used as a simple value. */\n} CssLengthType;\n\ninline CssLength CSS_CREATE_LENGTH (float v, CssLengthType t) {\n   static const int CSS_LENGTH_FRAC_MAX = (1 << (32 - 15 - 1)) - 1;\n   static const int CSS_LENGTH_INT_MAX = (1 << (32 - 4)) - 1;\n   int iv;\n\n   switch (t) {\n   case CSS_LENGTH_TYPE_PX:\n      iv = lout::misc::roundInt(v);\n      if (iv > CSS_LENGTH_INT_MAX)\n         iv = CSS_LENGTH_INT_MAX;\n      else if (iv < -CSS_LENGTH_INT_MAX)\n         iv = -CSS_LENGTH_INT_MAX;\n      return iv << 3 | t;\n   case CSS_LENGTH_TYPE_NONE:\n   case CSS_LENGTH_TYPE_MM:\n   case CSS_LENGTH_TYPE_EM:\n   case CSS_LENGTH_TYPE_EX:\n/* case CSS_LENGTH_TYPE_CH: */\n   case CSS_LENGTH_TYPE_PERCENTAGE:\n   case CSS_LENGTH_TYPE_RELATIVE:\n      if (v > CSS_LENGTH_FRAC_MAX)\n         v = CSS_LENGTH_FRAC_MAX;\n      else if (v < -CSS_LENGTH_FRAC_MAX)\n         v = -CSS_LENGTH_FRAC_MAX;\n      return ((int) (v * (1 << 15)) & ~7 ) | t;\n   case CSS_LENGTH_TYPE_AUTO:\n      return t;\n   default:\n      assert(false);\n      return CSS_LENGTH_TYPE_AUTO;\n   }\n}\n\ninline CssLengthType CSS_LENGTH_TYPE (CssLength l) {\n   return (CssLengthType) (l & 7);\n}\n\ninline float CSS_LENGTH_VALUE (CssLength l) {\n   switch (CSS_LENGTH_TYPE(l)) {\n   case CSS_LENGTH_TYPE_PX:\n      return (float) (l >> 3);\n   case CSS_LENGTH_TYPE_NONE:\n   case CSS_LENGTH_TYPE_MM:\n   case CSS_LENGTH_TYPE_EM:\n   case CSS_LENGTH_TYPE_EX:\n/* case CSS_LENGTH_TYPE_CH: */\n   case CSS_LENGTH_TYPE_PERCENTAGE:\n   case CSS_LENGTH_TYPE_RELATIVE:\n      return ((float)(l & ~7)) / (1 << 15);\n   case CSS_LENGTH_TYPE_AUTO:\n      return 0.0;\n   default:\n      assert(false);\n      return 0.0;\n   }\n}\n\ntypedef enum {\n   CSS_PROPERTY_END = -1, // used as terminator in CssShorthandInfo\n   CSS_PROPERTY_BACKGROUND_ATTACHMENT,\n   CSS_PROPERTY_BACKGROUND_COLOR,\n   CSS_PROPERTY_BACKGROUND_IMAGE,\n   CSS_PROPERTY_BACKGROUND_POSITION,\n   CSS_PROPERTY_BACKGROUND_REPEAT,\n   CSS_PROPERTY_BORDER_BOTTOM_COLOR,\n   CSS_PROPERTY_BORDER_BOTTOM_STYLE,\n   CSS_PROPERTY_BORDER_BOTTOM_WIDTH,\n   CSS_PROPERTY_BORDER_COLLAPSE,\n   CSS_PROPERTY_BORDER_LEFT_COLOR,\n   CSS_PROPERTY_BORDER_LEFT_STYLE,\n   CSS_PROPERTY_BORDER_LEFT_WIDTH,\n   CSS_PROPERTY_BORDER_RIGHT_COLOR,\n   CSS_PROPERTY_BORDER_RIGHT_STYLE,\n   CSS_PROPERTY_BORDER_RIGHT_WIDTH,\n   CSS_PROPERTY_BORDER_SPACING,\n   CSS_PROPERTY_BORDER_TOP_COLOR,\n   CSS_PROPERTY_BORDER_TOP_STYLE,\n   CSS_PROPERTY_BORDER_TOP_WIDTH,\n   CSS_PROPERTY_BOTTOM,\n   CSS_PROPERTY_CAPTION_SIDE,\n   CSS_PROPERTY_CLEAR,\n   CSS_PROPERTY_CLIP,\n   CSS_PROPERTY_COLOR,\n   CSS_PROPERTY_CONTENT,\n   CSS_PROPERTY_COUNTER_INCREMENT,\n   CSS_PROPERTY_COUNTER_RESET,\n   CSS_PROPERTY_CURSOR,\n   CSS_PROPERTY_DIRECTION,\n   CSS_PROPERTY_DISPLAY,\n   CSS_PROPERTY_EMPTY_CELLS,\n   CSS_PROPERTY_FLOAT,\n   CSS_PROPERTY_FONT_FAMILY,\n   CSS_PROPERTY_FONT_SIZE,\n   CSS_PROPERTY_FONT_SIZE_ADJUST,\n   CSS_PROPERTY_FONT_STRETCH,\n   CSS_PROPERTY_FONT_STYLE,\n   CSS_PROPERTY_FONT_VARIANT,\n   CSS_PROPERTY_FONT_WEIGHT,\n   CSS_PROPERTY_HEIGHT,\n   CSS_PROPERTY_LEFT,\n   CSS_PROPERTY_LETTER_SPACING,\n   CSS_PROPERTY_LINE_HEIGHT,\n   CSS_PROPERTY_LIST_STYLE_IMAGE,\n   CSS_PROPERTY_LIST_STYLE_POSITION,\n   CSS_PROPERTY_LIST_STYLE_TYPE,\n   CSS_PROPERTY_MARGIN_BOTTOM,\n   CSS_PROPERTY_MARGIN_LEFT,\n   CSS_PROPERTY_MARGIN_RIGHT,\n   CSS_PROPERTY_MARGIN_TOP,\n   CSS_PROPERTY_MARKER_OFFSET,\n   CSS_PROPERTY_MARKS,\n   CSS_PROPERTY_MAX_HEIGHT,\n   CSS_PROPERTY_MAX_WIDTH,\n   CSS_PROPERTY_MIN_HEIGHT,\n   CSS_PROPERTY_MIN_WIDTH,\n   CSS_PROPERTY_OUTLINE_COLOR,\n   CSS_PROPERTY_OUTLINE_STYLE,\n   CSS_PROPERTY_OUTLINE_WIDTH,\n   CSS_PROPERTY_OVERFLOW,\n   CSS_PROPERTY_PADDING_BOTTOM,\n   CSS_PROPERTY_PADDING_LEFT,\n   CSS_PROPERTY_PADDING_RIGHT,\n   CSS_PROPERTY_PADDING_TOP,\n   CSS_PROPERTY_POSITION,\n   CSS_PROPERTY_QUOTES,\n   CSS_PROPERTY_RIGHT,\n   CSS_PROPERTY_TEXT_ALIGN,\n   CSS_PROPERTY_TEXT_DECORATION,\n   CSS_PROPERTY_TEXT_INDENT,\n   CSS_PROPERTY_TEXT_SHADOW,\n   CSS_PROPERTY_TEXT_TRANSFORM,\n   CSS_PROPERTY_TOP,\n   CSS_PROPERTY_UNICODE_BIDI,\n   CSS_PROPERTY_VERTICAL_ALIGN,\n   CSS_PROPERTY_VISIBILITY,\n   CSS_PROPERTY_WHITE_SPACE,\n   CSS_PROPERTY_WIDTH,\n   CSS_PROPERTY_WORD_SPACING,\n   CSS_PROPERTY_Z_INDEX,\n   CSS_PROPERTY_X_LINK,\n   CSS_PROPERTY_X_COLSPAN,\n   CSS_PROPERTY_X_ROWSPAN,\n   PROPERTY_X_LINK,\n   PROPERTY_X_LANG,\n   PROPERTY_X_IMG,\n   PROPERTY_X_TOOLTIP,\n   CSS_PROPERTY_LAST\n} CssPropertyName;\n\ntypedef struct {\n   int32_t posX;\n   int32_t posY;\n} CssBackgroundPosition;\n\ntypedef union {\n   int32_t intVal;\n   char *strVal;\n   CssBackgroundPosition *posVal;\n} CssPropertyValue;\n\ntypedef enum {\n   CSS_BORDER_WIDTH_THIN,\n   CSS_BORDER_WIDTH_MEDIUM,\n   CSS_BORDER_WIDTH_THICK,\n} CssBorderWidthExtensions;\n\ntypedef enum {\n   CSS_FONT_WEIGHT_BOLD,\n   CSS_FONT_WEIGHT_BOLDER,\n   CSS_FONT_WEIGHT_LIGHT,\n   CSS_FONT_WEIGHT_LIGHTER,\n   CSS_FONT_WEIGHT_NORMAL,\n} CssFontWeightExtensions;\n\ntypedef enum {\n   CSS_FONT_SIZE_LARGE,\n   CSS_FONT_SIZE_LARGER,\n   CSS_FONT_SIZE_MEDIUM,\n   CSS_FONT_SIZE_SMALL,\n   CSS_FONT_SIZE_SMALLER,\n   CSS_FONT_SIZE_XX_LARGE,\n   CSS_FONT_SIZE_XX_SMALL,\n   CSS_FONT_SIZE_X_LARGE,\n   CSS_FONT_SIZE_X_SMALL,\n} CssFontSizeExtensions;\n\ntypedef enum {\n   CSS_LETTER_SPACING_NORMAL\n} CssLetterSpacingExtensions;\n\ntypedef enum {\n   CSS_WORD_SPACING_NORMAL\n} CssWordSpacingExtensions;\n\n\n/**\n * \\brief This class holds a CSS property and value pair.\n */\nclass CssProperty {\n   public:\n\n      short name;\n      short type;\n      CssPropertyValue value;\n\n      inline void free () {\n         switch (type) {\n            case CSS_TYPE_STRING:\n            case CSS_TYPE_SYMBOL:\n            case CSS_TYPE_URI:\n               dFree (value.strVal);\n               break;\n            case CSS_TYPE_BACKGROUND_POSITION:\n               dFree (value.posVal);\n            default:\n               break;\n         }\n      }\n      void print ();\n};\n\n/**\n * \\brief A list of CssProperty objects.\n */\nclass CssPropertyList : public lout::misc::SimpleVector <CssProperty> {\n   int refCount;\n   bool ownerOfStrings;\n   bool safe;\n\n   public:\n      inline CssPropertyList(bool ownerOfStrings = false) :\n                  lout::misc::SimpleVector <CssProperty> (1) {\n         refCount = 0;\n         safe = true;\n         this->ownerOfStrings = ownerOfStrings;\n      };\n      CssPropertyList(const CssPropertyList &p, bool deep = false);\n      ~CssPropertyList ();\n\n      void set (CssPropertyName name, CssValueType type,\n                CssPropertyValue value);\n      void apply (CssPropertyList *props);\n      bool isSafe () { return safe; };\n      void print ();\n      inline void ref () { refCount++; }\n      inline void unref () { if (--refCount == 0) delete this; }\n};\n\nclass CssSimpleSelector {\n   private:\n      int element;\n      char *pseudo, *id;\n      lout::misc::SimpleVector <char *> klass;\n\n   public:\n      enum {\n         ELEMENT_NONE = -1,\n         ELEMENT_ANY = -2,\n      };\n\n      typedef enum {\n         SELECT_NONE,\n         SELECT_CLASS,\n         SELECT_PSEUDO_CLASS,\n         SELECT_ID,\n      } SelectType;\n\n      CssSimpleSelector ();\n      ~CssSimpleSelector ();\n      inline void setElement (int e) { element = e; };\n      void setSelect (SelectType t, const char *v);\n      inline lout::misc::SimpleVector <char *> *getClass () { return &klass; };\n      inline const char *getPseudoClass () { return pseudo; };\n      inline const char *getId () { return id; };\n      inline int getElement () { return element; };\n      bool match (const DoctreeNode *node);\n      int specificity ();\n      void print ();\n};\n\nclass MatchCache : public lout::misc::SimpleVector <int> {\n   public:\n      MatchCache() : lout::misc::SimpleVector <int> (0) {};\n};\n\n/**\n * \\brief CSS selector class.\n *\n * \\todo Implement missing selector options.\n */\nclass CssSelector {\n   public:\n      typedef enum {\n         COMB_NONE,\n         COMB_DESCENDANT,\n         COMB_CHILD,\n         COMB_ADJACENT_SIBLING,\n      } Combinator;\n\n   private:\n      struct CombinatorAndSelector {\n         Combinator combinator;\n         CssSimpleSelector *selector;\n      };\n\n      int refCount, matchCacheOffset;\n      lout::misc::SimpleVector <struct CombinatorAndSelector> selectorList;\n\n      bool match (Doctree *dt, const DoctreeNode *node, int i, Combinator comb,\n                  MatchCache *matchCache);\n\n   public:\n      CssSelector ();\n      ~CssSelector ();\n      void addSimpleSelector (Combinator c);\n      inline CssSimpleSelector *top () {\n         return selectorList.getRef (selectorList.size () - 1)->selector;\n      }\n      inline int size () { return selectorList.size (); };\n      inline bool match (Doctree *dt, const DoctreeNode *node,\n                         MatchCache *matchCache) {\n         return match (dt, node, selectorList.size () - 1, COMB_NONE,\n                       matchCache);\n      }\n      inline void setMatchCacheOffset (int mo) {\n         if (matchCacheOffset == -1)\n            matchCacheOffset = mo;\n      }\n      inline int getRequiredMatchCache () {\n         return matchCacheOffset + size ();\n      }\n      int specificity ();\n      bool checksPseudoClass ();\n      void print ();\n      inline void ref () { refCount++; }\n      inline void unref () { if (--refCount == 0) delete this; }\n};\n\n/**\n * \\brief A CssSelector CssPropertyList pair.\n *\n *  The CssPropertyList is applied if the CssSelector matches.\n */\nclass CssRule {\n   private:\n      CssPropertyList *props;\n      int spec, pos;\n\n   public:\n      CssSelector *selector;\n\n      CssRule (CssSelector *selector, CssPropertyList *props, int pos);\n      ~CssRule ();\n\n      void apply (CssPropertyList *props, Doctree *docTree,\n                  const DoctreeNode *node, MatchCache *matchCache) const;\n      inline bool isSafe () {\n         return !selector->checksPseudoClass () || props->isSafe ();\n      };\n      inline int specificity () { return spec; };\n      inline int position () { return pos; };\n      void print ();\n};\n\n/**\n * \\brief A list of CssRules.\n *\n * In apply () all matching rules are applied.\n */\nclass CssStyleSheet {\n   private:\n      class RuleList : public lout::misc::SimpleVector <CssRule*>,\n                       public lout::object::Object {\n         public:\n            RuleList () : lout::misc::SimpleVector <CssRule*> (1) {};\n            ~RuleList () {\n               for (int i = 0; i < size (); i++)\n                  delete get (i);\n            };\n\n            void insert (CssRule *rule);\n            inline bool equals (lout::object::Object *other) {\n               return this == other;\n            };\n            inline int hashValue () { return (intptr_t) this; };\n      };\n\n      class RuleMap : public lout::container::typed::HashTable\n                             <lout::object::ConstString, RuleList > {\n         public:\n            RuleMap () : lout::container::typed::HashTable\n               <lout::object::ConstString, RuleList > (true, true, 256) {};\n      };\n\n      static const int ntags = 90 + 14; // \\todo don't hardcode\n      /* 90 is the full number of html4 elements, including those which we have\n       * implemented. From html5, let's add: article, header, footer, mark,\n       * nav, section, aside, figure, figcaption, wbr, audio, video, source,\n       * embed.\n       */\n\n      RuleList elementTable[ntags], anyTable;\n      RuleMap idTable, classTable;\n      int requiredMatchCache;\n\n   public:\n      CssStyleSheet () { requiredMatchCache = 0; }\n      void addRule (CssRule *rule);\n      void apply (CssPropertyList *props, Doctree *docTree,\n                  const DoctreeNode *node, MatchCache *matchCache) const;\n      int getRequiredMatchCache () { return requiredMatchCache; }\n};\n\n/**\n * \\brief A set of CssStyleSheets.\n */\nclass CssContext {\n   private:\n      static CssStyleSheet userAgentSheet;\n      CssStyleSheet sheet[CSS_PRIMARY_USER_IMPORTANT + 1];\n      MatchCache matchCache;\n      int pos;\n\n   public:\n      CssContext ();\n\n      void addRule (CssSelector *sel, CssPropertyList *props,\n                    CssPrimaryOrder order);\n      void apply (CssPropertyList *props,\n         Doctree *docTree, DoctreeNode *node,\n         CssPropertyList *tagStyle, CssPropertyList *tagStyleImportant,\n         CssPropertyList *nonCssHints);\n};\n\n#endif\n"
  },
  {
    "path": "src/cssparser.cc",
    "content": "/*\n * File: cssparser.cc\n *\n * Copyright 2004 Sebastian Geerken <sgeerken@dillo.org>\n * Copyright 2008-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * This file is heavily based on the CSS parser of dillo-0.8.0-css-3 -\n * a dillo1 based CSS prototype written by Sebastian Geerken.\n */\n\n#include <ctype.h>\n#include <stdlib.h>\n#include <stdio.h>\n\n#include \"msg.h\"\n#include \"colors.h\"\n#include \"html_common.hh\"\n#include \"css.hh\"\n#include \"cssparser.hh\"\n\nusing namespace dw::core::style;\n\n#define MSG_CSS(A, ...) MSG(A, __VA_ARGS__)\n#define DEBUG_TOKEN_LEVEL   0\n#define DEBUG_PARSE_LEVEL   0\n#define DEBUG_CREATE_LEVEL  0\n\n#define DEBUG_LEVEL 10\n\n/* The last three ones are never parsed. */\n#define CSS_NUM_INTERNAL_PROPERTIES 3\n#define CSS_NUM_PARSED_PROPERTIES \\\n   (CSS_PROPERTY_LAST - CSS_NUM_INTERNAL_PROPERTIES)\n\n\ntypedef struct {\n   const char *symbol;\n   const CssValueType type[3];\n   const char *const *enum_symbols;\n} CssPropertyInfo;\n\nstatic const char *const Css_background_attachment_enum_vals[] = {\n   \"scroll\", \"fixed\", NULL\n};\n\nstatic const char *const Css_background_repeat_enum_vals[] = {\n   \"repeat\", \"repeat-x\", \"repeat-y\", \"no-repeat\", NULL\n};\n\nstatic const char *const Css_border_collapse_enum_vals[] = {\n   \"separate\", \"collapse\", NULL\n};\n\nstatic const char *const Css_border_color_enum_vals[] = {\n   \"transparent\", NULL\n};\n\nstatic const char *const Css_border_style_enum_vals[] = {\n   \"none\", \"hidden\", \"dotted\", \"dashed\", \"solid\", \"double\", \"groove\",\n   \"ridge\", \"inset\", \"outset\", NULL\n};\n\nstatic const char *const Css_border_width_enum_vals[] = {\n   \"thin\", \"medium\", \"thick\", NULL\n};\n\nstatic const char *const Css_clear_enum_vals[] = {\n   \"left\", \"right\", \"both\", \"none\", NULL\n};\n\nstatic const char *const Css_cursor_enum_vals[] = {\n   \"crosshair\", \"default\", \"pointer\", \"move\", \"e-resize\", \"ne-resize\",\n   \"nw-resize\", \"n-resize\", \"se-resize\", \"sw-resize\", \"s-resize\",\n   \"w-resize\", \"text\", \"wait\", \"help\", NULL\n};\n\nstatic const char *const Css_display_enum_vals[] = {\n   \"block\", \"inline\", \"inline-block\", \"list-item\", \"none\", \"table\",\n   \"table-row-group\", \"table-header-group\", \"table-footer-group\", \"table-row\",\n   \"table-cell\", NULL\n};\n\nstatic const char *const Css_float_enum_vals[] = {\n   \"none\", \"left\", \"right\", NULL\n};\n\nstatic const char *const Css_font_size_enum_vals[] = {\n   \"large\", \"larger\", \"medium\", \"small\", \"smaller\", \"xx-large\", \"xx-small\",\n   \"x-large\", \"x-small\", NULL\n};\n\nstatic const char *const Css_font_style_enum_vals[] = {\n   \"normal\", \"italic\", \"oblique\", NULL\n};\n\nstatic const char *const Css_font_variant_enum_vals[] = {\n   \"normal\", \"small-caps\", NULL\n};\n\nstatic const char *const Css_font_weight_enum_vals[] = {\n   \"bold\", \"bolder\", \"light\", \"lighter\", \"normal\", NULL\n};\n\nstatic const char *const Css_letter_spacing_enum_vals[] = {\n   \"normal\", NULL\n};\n\nstatic const char *const Css_list_style_position_enum_vals[] = {\n   \"inside\", \"outside\", NULL\n};\n\nstatic const char *const Css_line_height_enum_vals[] = {\n   \"normal\", NULL\n};\n\nstatic const char *const Css_list_style_type_enum_vals[] = {\n   \"disc\", \"circle\", \"square\", \"decimal\", \"decimal-leading-zero\",\n   \"lower-roman\", \"upper-roman\", \"lower-greek\", \"lower-alpha\",\n   \"lower-latin\", \"upper-alpha\", \"upper-latin\", \"hebrew\", \"armenian\",\n   \"georgian\", \"cjk-ideographic\", \"hiragana\", \"katakana\", \"hiragana-iroha\",\n   \"katakana-iroha\", \"none\", NULL\n};\n\nstatic const char *const Css_overflow_enum_vals[] = {\n   \"visible\", \"hidden\", \"scroll\", \"auto\", NULL\n};\n\nstatic const char *const Css_position_enum_vals[] = {\n   \"static\", \"relative\", \"absolute\", \"fixed\", NULL\n};\n\nstatic const char *const Css_text_align_enum_vals[] = {\n   \"left\", \"right\", \"center\", \"justify\", \"string\", NULL\n};\n\nstatic const char *const Css_text_decoration_enum_vals[] = {\n   \"underline\", \"overline\", \"line-through\", \"blink\", NULL\n};\n\nstatic const char *const Css_text_transform_enum_vals[] = {\n   \"none\", \"capitalize\", \"uppercase\", \"lowercase\", NULL\n};\n\nstatic const char *const Css_vertical_align_vals[] = {\n   \"top\", \"bottom\", \"middle\", \"baseline\", \"sub\", \"super\", \"text-top\",\n   \"text-bottom\", NULL\n};\n\nstatic const char *const Css_white_space_vals[] = {\n   \"normal\", \"pre\", \"nowrap\", \"pre-wrap\", \"pre-line\", NULL\n};\n\nstatic const char *const Css_word_spacing_enum_vals[] = {\n   \"normal\", NULL\n};\n\nconst CssPropertyInfo Css_property_info[CSS_PROPERTY_LAST] = {\n   {\"background-attachment\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_background_attachment_enum_vals},\n   {\"background-color\", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL},\n   {\"background-image\", {CSS_TYPE_URI, CSS_TYPE_UNUSED}, NULL},\n   {\"background-position\", {CSS_TYPE_BACKGROUND_POSITION, CSS_TYPE_UNUSED},\n    NULL},\n   {\"background-repeat\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_background_repeat_enum_vals},\n   {\"border-bottom-color\", {CSS_TYPE_ENUM, CSS_TYPE_COLOR, CSS_TYPE_UNUSED},\n    Css_border_color_enum_vals},\n   {\"border-bottom-style\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_border_style_enum_vals},\n   {\"border-bottom-width\", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED},\n    Css_border_width_enum_vals},\n   {\"border-collapse\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_border_collapse_enum_vals},\n   {\"border-left-color\", {CSS_TYPE_ENUM, CSS_TYPE_COLOR, CSS_TYPE_UNUSED},\n    Css_border_color_enum_vals},\n   {\"border-left-style\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_border_style_enum_vals},\n   {\"border-left-width\", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED},\n    Css_border_width_enum_vals},\n   {\"border-right-color\", {CSS_TYPE_ENUM, CSS_TYPE_COLOR, CSS_TYPE_UNUSED},\n    Css_border_color_enum_vals},\n   {\"border-right-style\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_border_style_enum_vals},\n   {\"border-rigth-width\", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED},\n    Css_border_width_enum_vals},\n   {\"border-spacing\", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"border-top-color\", {CSS_TYPE_ENUM, CSS_TYPE_COLOR, CSS_TYPE_UNUSED},\n    Css_border_color_enum_vals},\n   {\"border-top-style\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_border_style_enum_vals},\n   {\"border-top-width\", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH, CSS_TYPE_UNUSED},\n    Css_border_width_enum_vals},\n   {\"bottom\", {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"caption-side\", {CSS_TYPE_UNUSED}, NULL},\n   {\"clear\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_clear_enum_vals},\n   {\"clip\", {CSS_TYPE_UNUSED}, NULL},\n   {\"color\", {CSS_TYPE_COLOR, CSS_TYPE_UNUSED}, NULL},\n   {\"content\", {CSS_TYPE_STRING, CSS_TYPE_UNUSED}, NULL},\n   {\"counter-increment\", {CSS_TYPE_UNUSED}, NULL},\n   {\"counter-reset\", {CSS_TYPE_UNUSED}, NULL},\n   {\"cursor\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_cursor_enum_vals},\n   {\"direction\", {CSS_TYPE_UNUSED}, NULL},\n   {\"display\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_display_enum_vals},\n   {\"empty-cells\", {CSS_TYPE_UNUSED}, NULL},\n   {\"float\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_float_enum_vals},\n   {\"font-family\", {CSS_TYPE_SYMBOL, CSS_TYPE_UNUSED}, NULL},\n   {\"font-size\", {CSS_TYPE_ENUM, CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED},\n    Css_font_size_enum_vals},\n   {\"font-size-adjust\", {CSS_TYPE_UNUSED}, NULL},\n   {\"font-stretch\", {CSS_TYPE_UNUSED}, NULL},\n   {\"font-style\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_font_style_enum_vals},\n   {\"font-variant\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_font_variant_enum_vals},\n   {\"font-weight\", {CSS_TYPE_ENUM, CSS_TYPE_FONT_WEIGHT, CSS_TYPE_UNUSED},\n    Css_font_weight_enum_vals},\n   {\"height\", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL},\n   {\"left\", {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"letter-spacing\", {CSS_TYPE_ENUM, CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED},\n    Css_letter_spacing_enum_vals},\n   {\"line-height\",\n    {CSS_TYPE_ENUM, CSS_TYPE_LENGTH_PERCENTAGE_NUMBER, CSS_TYPE_UNUSED},\n    Css_line_height_enum_vals},\n   {\"list-style-image\", {CSS_TYPE_UNUSED}, NULL},\n   {\"list-style-position\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_list_style_position_enum_vals},\n   {\"list-style-type\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_list_style_type_enum_vals},\n   {\"margin-bottom\",\n    {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL},\n   {\"margin-left\",\n    {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL},\n   {\"margin-right\",\n    {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL},\n   {\"margin-top\",\n    {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL},\n   {\"marker-offset\", {CSS_TYPE_UNUSED}, NULL},\n   {\"marks\", {CSS_TYPE_UNUSED}, NULL},\n   {\"max-height\", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED},\n    NULL},\n   {\"max-width\", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED},\n    NULL},\n   {\"min-height\", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED},\n    NULL},\n   {\"min-width\", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED},\n    NULL},\n   {\"outline-color\", {CSS_TYPE_UNUSED}, NULL},\n   {\"outline-style\", {CSS_TYPE_UNUSED}, NULL},\n   {\"outline-width\", {CSS_TYPE_UNUSED}, NULL},\n   {\"overflow\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_overflow_enum_vals},\n   {\"padding-bottom\", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"padding-left\", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"padding-right\", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"padding-top\", {CSS_TYPE_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"position\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_position_enum_vals},\n   {\"quotes\", {CSS_TYPE_UNUSED}, NULL},\n   {\"right\", {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"text-align\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_text_align_enum_vals},\n   {\"text-decoration\", {CSS_TYPE_MULTI_ENUM, CSS_TYPE_UNUSED},\n    Css_text_decoration_enum_vals},\n   {\"text-indent\", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_UNUSED}, NULL},\n   {\"text-shadow\", {CSS_TYPE_UNUSED}, NULL},\n   {\"text-transform\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED},\n    Css_text_transform_enum_vals},\n   {\"top\", {CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED}, NULL},\n   {\"unicode-bidi\", {CSS_TYPE_UNUSED}, NULL},\n   {\"vertical-align\",{CSS_TYPE_ENUM, CSS_TYPE_UNUSED},Css_vertical_align_vals},\n   {\"visibility\", {CSS_TYPE_UNUSED}, NULL},\n   {\"white-space\", {CSS_TYPE_ENUM, CSS_TYPE_UNUSED}, Css_white_space_vals},\n   {\"width\", {CSS_TYPE_LENGTH_PERCENTAGE, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL},\n   {\"word-spacing\", {CSS_TYPE_ENUM, CSS_TYPE_SIGNED_LENGTH, CSS_TYPE_UNUSED},\n    Css_word_spacing_enum_vals},\n   {\"z-index\", {CSS_TYPE_INTEGER, CSS_TYPE_AUTO, CSS_TYPE_UNUSED}, NULL},\n\n   /* These are extensions, for internal used, and never parsed. */\n   {\"x-link\", {CSS_TYPE_INTEGER, CSS_TYPE_UNUSED}, NULL},\n   {\"x-colspan\", {CSS_TYPE_INTEGER, CSS_TYPE_UNUSED}, NULL},\n   {\"x-rowspan\", {CSS_TYPE_INTEGER, CSS_TYPE_UNUSED}, NULL},\n   {\"last\", {CSS_TYPE_UNUSED}, NULL},\n};\n\ntypedef struct {\n   const char *symbol;\n   enum {\n      CSS_SHORTHAND_MULTIPLE,   /* [ p1 || p2 || ...], the property pi is\n                                 * determined  by the type */\n      CSS_SHORTHAND_DIRECTIONS, /* <t>{1,4} */\n      CSS_SHORTHAND_BORDER,     /* special, used for 'border' */\n      CSS_SHORTHAND_FONT,       /* special, used for 'font' */\n   } type;\n   const CssPropertyName *properties; /* CSS_SHORTHAND_MULTIPLE:\n                                       *   must be terminated by\n                                       *   CSS_PROPERTY_END\n                                       * CSS_SHORTHAND_DIRECTIONS:\n                                       *   must have length 4\n                                       * CSS_SHORTHAND_BORDERS:\n                                       *   must have length 12\n                                       * CSS_SHORTHAND_FONT:\n                                       *   unused */\n} CssShorthandInfo;\n\nconst CssPropertyName Css_background_properties[] = {\n   CSS_PROPERTY_BACKGROUND_COLOR,\n   CSS_PROPERTY_BACKGROUND_IMAGE,\n   CSS_PROPERTY_BACKGROUND_REPEAT,\n   CSS_PROPERTY_BACKGROUND_ATTACHMENT,\n   CSS_PROPERTY_BACKGROUND_POSITION,\n   CSS_PROPERTY_END\n};\n\nconst CssPropertyName Css_border_bottom_properties[] = {\n   CSS_PROPERTY_BORDER_BOTTOM_WIDTH,\n   CSS_PROPERTY_BORDER_BOTTOM_STYLE,\n   CSS_PROPERTY_BORDER_BOTTOM_COLOR,\n   CSS_PROPERTY_END\n};\n\nconst CssPropertyName Css_border_color_properties[4] = {\n   CSS_PROPERTY_BORDER_TOP_COLOR,\n   CSS_PROPERTY_BORDER_BOTTOM_COLOR,\n   CSS_PROPERTY_BORDER_LEFT_COLOR,\n   CSS_PROPERTY_BORDER_RIGHT_COLOR\n};\n\nconst CssPropertyName Css_border_left_properties[] = {\n   CSS_PROPERTY_BORDER_LEFT_WIDTH,\n   CSS_PROPERTY_BORDER_LEFT_STYLE,\n   CSS_PROPERTY_BORDER_LEFT_COLOR,\n   CSS_PROPERTY_END\n};\n\nconst CssPropertyName Css_border_right_properties[] = {\n   CSS_PROPERTY_BORDER_RIGHT_WIDTH,\n   CSS_PROPERTY_BORDER_RIGHT_STYLE,\n   CSS_PROPERTY_BORDER_RIGHT_COLOR,\n   CSS_PROPERTY_END\n};\n\nconst CssPropertyName Css_border_style_properties[] = {\n   CSS_PROPERTY_BORDER_TOP_STYLE,\n   CSS_PROPERTY_BORDER_BOTTOM_STYLE,\n   CSS_PROPERTY_BORDER_LEFT_STYLE,\n   CSS_PROPERTY_BORDER_RIGHT_STYLE\n};\n\nconst CssPropertyName Css_border_top_properties[] = {\n   CSS_PROPERTY_BORDER_TOP_WIDTH,\n   CSS_PROPERTY_BORDER_TOP_STYLE,\n   CSS_PROPERTY_BORDER_TOP_COLOR,\n   CSS_PROPERTY_END\n};\n\nconst CssPropertyName Css_border_width_properties[] = {\n   CSS_PROPERTY_BORDER_TOP_WIDTH,\n   CSS_PROPERTY_BORDER_BOTTOM_WIDTH,\n   CSS_PROPERTY_BORDER_LEFT_WIDTH,\n   CSS_PROPERTY_BORDER_RIGHT_WIDTH\n};\n\nconst CssPropertyName Css_list_style_properties[] = {\n   CSS_PROPERTY_LIST_STYLE_TYPE,\n   CSS_PROPERTY_LIST_STYLE_POSITION,\n   CSS_PROPERTY_LIST_STYLE_IMAGE,\n   CSS_PROPERTY_END\n};\n\nconst CssPropertyName Css_margin_properties[] = {\n   CSS_PROPERTY_MARGIN_TOP,\n   CSS_PROPERTY_MARGIN_BOTTOM,\n   CSS_PROPERTY_MARGIN_LEFT,\n   CSS_PROPERTY_MARGIN_RIGHT\n};\n\nconst CssPropertyName Css_outline_properties[] = {\n   CSS_PROPERTY_OUTLINE_COLOR,\n   CSS_PROPERTY_OUTLINE_STYLE,\n   CSS_PROPERTY_OUTLINE_WIDTH,\n   CSS_PROPERTY_END\n};\n\nconst CssPropertyName Css_padding_properties[] = {\n   CSS_PROPERTY_PADDING_TOP,\n   CSS_PROPERTY_PADDING_BOTTOM,\n   CSS_PROPERTY_PADDING_LEFT,\n   CSS_PROPERTY_PADDING_RIGHT\n};\n\nconst CssPropertyName Css_border_properties[] = {\n   CSS_PROPERTY_BORDER_TOP_WIDTH,\n   CSS_PROPERTY_BORDER_TOP_STYLE,\n   CSS_PROPERTY_BORDER_TOP_COLOR,\n   CSS_PROPERTY_BORDER_BOTTOM_WIDTH,\n   CSS_PROPERTY_BORDER_BOTTOM_STYLE,\n   CSS_PROPERTY_BORDER_BOTTOM_COLOR,\n   CSS_PROPERTY_BORDER_LEFT_WIDTH,\n   CSS_PROPERTY_BORDER_LEFT_STYLE,\n   CSS_PROPERTY_BORDER_LEFT_COLOR,\n   CSS_PROPERTY_BORDER_RIGHT_WIDTH,\n   CSS_PROPERTY_BORDER_RIGHT_STYLE,\n   CSS_PROPERTY_BORDER_RIGHT_COLOR\n};\n\nconst CssPropertyName Css_font_properties[] = {\n   CSS_PROPERTY_FONT_SIZE,\n   CSS_PROPERTY_FONT_STYLE,\n   CSS_PROPERTY_FONT_VARIANT,\n   CSS_PROPERTY_FONT_WEIGHT,\n   CSS_PROPERTY_FONT_FAMILY,\n   CSS_PROPERTY_END\n};\n\nstatic const CssShorthandInfo Css_shorthand_info[] = {\n   {\"background\", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE,\n    Css_background_properties},\n   {\"border\", CssShorthandInfo::CSS_SHORTHAND_BORDER,\n    Css_border_properties},\n   {\"border-bottom\", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE,\n    Css_border_bottom_properties},\n   {\"border-color\", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS,\n    Css_border_color_properties},\n   {\"border-left\", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE,\n    Css_border_left_properties},\n   {\"border-right\", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE,\n    Css_border_right_properties},\n   {\"border-style\", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS,\n    Css_border_style_properties},\n   {\"border-top\", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE,\n    Css_border_top_properties},\n   {\"border-width\", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS,\n    Css_border_width_properties},\n   {\"font\", CssShorthandInfo::CSS_SHORTHAND_FONT,\n    Css_font_properties},\n   {\"list-style\", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE,\n    Css_list_style_properties},\n   {\"margin\", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS,\n    Css_margin_properties},\n   {\"outline\", CssShorthandInfo::CSS_SHORTHAND_MULTIPLE,\n    Css_outline_properties},\n   {\"padding\", CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS,\n    Css_padding_properties},\n};\n\n#define CSS_SHORTHAND_NUM \\\n   (sizeof(Css_shorthand_info) / sizeof(Css_shorthand_info[0]))\n\n/* ----------------------------------------------------------------------\n *    Parsing\n * ---------------------------------------------------------------------- */\n\nCssParser::CssParser(CssContext *context, CssOrigin origin,\n                     const DilloUrl *baseUrl,\n                     const char *buf, int buflen)\n{\n   this->context = context;\n   this->origin = origin;\n   this->buf = buf;\n   this->buflen = buflen;\n   this->bufptr = 0;\n   this->spaceSeparated = false;\n   this->withinBlock = false;\n   this->baseUrl = baseUrl;\n\n   nextToken ();\n}\n\n/*\n * Gets the next character from the buffer, or EOF.\n */\nint CssParser::getChar()\n{\n   int c;\n\n   if (bufptr >= buflen)\n      c = EOF;\n   else\n      c = buf[bufptr];\n\n   /* The buffer pointer is increased in any case, so that ungetChar works\n    * correctly at the end of the buffer. */\n   bufptr++;\n   return c;\n}\n\n/*\n * Undoes the last getChar().\n */\nvoid CssParser::ungetChar()\n{\n   bufptr--;\n}\n\n/*\n * Skip string str if it is found in the input buffer.\n * If string is found leave bufptr pointing to last matched char.\n * If not wind back. The first char is passed as parameter c\n * to avoid unnecessary getChar() / ungetChar() calls.\n */\ninline bool CssParser::skipString(int c, const char *str)\n{\n   for (int n = 0; str[n]; n++) {\n      if (n > 0)\n         c = getChar();\n\n      if (str[n] != c) {\n         while (n--)\n            ungetChar();\n         return false;\n      }\n   }\n\n   return true;\n}\n\nvoid CssParser::nextToken()\n{\n   int c, c1, d, j;\n   char hexbuf[5];\n   int i = 0;\n\n   ttype = CSS_TK_CHAR; /* init */\n   spaceSeparated = false;\n\n   while (true) {\n      c = getChar();\n      if (isspace(c)) {                    // ignore whitespace\n         spaceSeparated = true;\n      } else if (skipString(c, \"/*\")) {    // ignore comments\n         do {\n            c = getChar();\n         } while (c != EOF && ! skipString(c, \"*/\"));\n      } else if (skipString(c, \"<!--\")) {  // ignore XML comment markers\n      } else if (skipString(c, \"-->\")) {\n      } else {\n         break;\n      }\n   }\n\n   // handle negative numbers\n   if (c == '-') {\n      if (i < maxStrLen - 1)\n         tval[i++] = c;\n      c = getChar();\n   }\n\n   if (isdigit(c)) {\n      ttype = CSS_TK_DECINT;\n      do {\n         if (i < maxStrLen - 1) {\n            tval[i++] = c;\n         }\n         /* else silently truncated */\n         c = getChar();\n      } while (isdigit(c));\n      if (c != '.')\n         ungetChar();\n\n      /* ...but keep going to see whether it's really a float */\n   }\n\n   if (c == '.') {\n      c = getChar();\n      if (isdigit(c)) {\n         ttype = CSS_TK_FLOAT;\n         if (i < maxStrLen - 1)\n            tval[i++] = '.';\n         do {\n            if (i < maxStrLen - 1)\n               tval[i++] = c;\n            /* else silently truncated */\n            c = getChar();\n         } while (isdigit(c));\n\n         ungetChar();\n         tval[i] = 0;\n         DEBUG_MSG(DEBUG_TOKEN_LEVEL, \"token number %s\\n\", tval);\n         return;\n      } else {\n         ungetChar();\n         if (ttype == CSS_TK_DECINT) {\n            ungetChar();\n         } else {\n            c = '.';\n         }\n      }\n   }\n\n   if (ttype == CSS_TK_DECINT) {\n      tval[i] = 0;\n      DEBUG_MSG(DEBUG_TOKEN_LEVEL, \"token number %s\\n\", tval);\n      return;\n   }\n\n   if (i) {\n      ungetChar(); /* ungetChar '-' */\n      i--;\n      c = getChar();\n   }\n\n   if (isalpha(c) || c == '_' || c == '-') {\n      ttype = CSS_TK_SYMBOL;\n\n      tval[0] = c;\n      i = 1;\n      c = getChar();\n      while (isalnum(c) || c == '_' || c == '-') {\n         if (i < maxStrLen - 1) {\n            tval[i] = c;\n            i++;\n         }                      /* else silently truncated */\n         c = getChar();\n      }\n      tval[i] = 0;\n      ungetChar();\n      DEBUG_MSG(DEBUG_TOKEN_LEVEL, \"token symbol '%s'\\n\", tval);\n      return;\n   }\n\n   if (c == '\"' || c == '\\'') {\n      c1 = c;\n      ttype = CSS_TK_STRING;\n\n      i = 0;\n      c = getChar();\n\n      while (c != EOF && c != c1) {\n         if (c == '\\\\') {\n            d = getChar();\n            if (isxdigit(d)) {\n               /* Read hex Unicode char. (Actually, strings are yet only 8\n                * bit.) */\n               hexbuf[0] = d;\n               j = 1;\n               d = getChar();\n               while (j < 4 && isxdigit(d)) {\n                  hexbuf[j] = d;\n                  j++;\n                  d = getChar();\n               }\n               hexbuf[j] = 0;\n               ungetChar();\n               c = strtol(hexbuf, NULL, 16);\n            } else {\n               /* Take character literally. */\n               c = d;\n            }\n         }\n\n         if (i < maxStrLen - 1) {\n            tval[i] = c;\n            i++;\n         }                      /* else silently truncated */\n         c = getChar();\n      }\n      tval[i] = 0;\n      /* No ungetChar(). */\n      DEBUG_MSG(DEBUG_TOKEN_LEVEL, \"token string '%s'\\n\", tval);\n      return;\n   }\n\n   /*\n    * Within blocks, '#' starts a color, outside, it is used in selectors.\n    */\n   if (c == '#' && withinBlock) {\n      ttype = CSS_TK_COLOR;\n\n      tval[0] = c;\n      i = 1;\n      c = getChar();\n      while (isxdigit(c)) {\n         if (i < maxStrLen - 1) {\n            tval[i] = c;\n            i++;\n         }                      /* else silently truncated */\n         c = getChar();\n      }\n      tval[i] = 0;\n      ungetChar();\n      DEBUG_MSG(DEBUG_TOKEN_LEVEL, \"token color '%s'\\n\", tval);\n      return;\n   }\n\n   if (c == EOF) {\n      DEBUG_MSG(DEBUG_TOKEN_LEVEL, \"token %s\\n\", \"EOF\");\n      ttype = CSS_TK_END;\n      return;\n   }\n\n   ttype = CSS_TK_CHAR;\n   tval[0] = c;\n   tval[1] = 0;\n   DEBUG_MSG(DEBUG_TOKEN_LEVEL, \"token char '%c'\\n\", c);\n}\n\n\nbool CssParser::tokenMatchesProperty(CssPropertyName prop, CssValueType *type)\n{\n   int i, err = 1;\n   CssValueType savedType = *type;\n\n   for (int j = 0; Css_property_info[prop].type[j] != CSS_TYPE_UNUSED; j++) {\n      *type = Css_property_info[prop].type[j];\n\n      switch (Css_property_info[prop].type[j]) {\n\n      case CSS_TYPE_ENUM:\n         if (ttype == CSS_TK_SYMBOL) {\n            for (i = 0; Css_property_info[prop].enum_symbols[i]; i++)\n               if (dStrAsciiCasecmp(tval,\n                     Css_property_info[prop].enum_symbols[i]) == 0)\n                  return true;\n         }\n         break;\n\n      case CSS_TYPE_MULTI_ENUM:\n         if (ttype == CSS_TK_SYMBOL) {\n            if (dStrAsciiCasecmp(tval, \"none\") == 0) {\n               return true;\n            } else {\n               for (i = 0; Css_property_info[prop].enum_symbols[i]; i++) {\n                  if (dStrAsciiCasecmp(tval,\n                        Css_property_info[prop].enum_symbols[i]) == 0)\n                     return true;\n               }\n            }\n         }\n         break;\n\n      case CSS_TYPE_BACKGROUND_POSITION:\n         if (ttype == CSS_TK_SYMBOL &&\n             (dStrAsciiCasecmp(tval, \"center\") == 0 ||\n              dStrAsciiCasecmp(tval, \"left\") == 0 ||\n              dStrAsciiCasecmp(tval, \"right\") == 0 ||\n              dStrAsciiCasecmp(tval, \"top\") == 0 ||\n              dStrAsciiCasecmp(tval, \"bottom\") == 0))\n            return true;\n         if (ttype == CSS_TK_DECINT || ttype == CSS_TK_FLOAT)\n            return true;\n         break;\n      case CSS_TYPE_LENGTH_PERCENTAGE:\n      case CSS_TYPE_LENGTH_PERCENTAGE_NUMBER:\n      case CSS_TYPE_LENGTH:\n         if (tval[0] == '-')\n            return false;\n         // Fall Through\n      case CSS_TYPE_SIGNED_LENGTH:\n         if (ttype == CSS_TK_DECINT || ttype == CSS_TK_FLOAT)\n            return true;\n         break;\n\n      case CSS_TYPE_AUTO:\n         if (ttype == CSS_TK_SYMBOL && dStrAsciiCasecmp(tval, \"auto\") == 0)\n            return true;\n         break;\n\n      case CSS_TYPE_COLOR:\n         if ((ttype == CSS_TK_COLOR ||\n              ttype == CSS_TK_SYMBOL) &&\n            (dStrAsciiCasecmp(tval, \"rgb\") == 0 ||\n             a_Color_parse(tval, -1, &err) != -1))\n            return true;\n         break;\n\n      case CSS_TYPE_STRING:\n         if (ttype == CSS_TK_STRING)\n            return true;\n         break;\n\n      case CSS_TYPE_SYMBOL:\n         if (ttype == CSS_TK_SYMBOL ||\n             ttype == CSS_TK_STRING)\n            return true;\n         break;\n\n      case CSS_TYPE_FONT_WEIGHT:\n         if (ttype == CSS_TK_DECINT) {\n            i = strtol(tval, NULL, 10);\n            if (i >= 100 && i <= 900)\n               return true;\n         }\n         break;\n\n      case CSS_TYPE_URI:\n         if (ttype == CSS_TK_SYMBOL &&\n             (dStrAsciiCasecmp(tval, \"url\") == 0 ||\n              dStrAsciiCasecmp(tval, \"none\") == 0))\n            return true;\n         break;\n\n      case CSS_TYPE_INTEGER:\n         if (ttype == CSS_TK_DECINT)\n            return true;\n         break;\n\n      case CSS_TYPE_UNUSED:\n      default:\n         assert(false);\n         break;\n      }\n   }\n\n   *type = savedType;\n   return false;\n}\n\nbool CssParser::parseRgbColorComponent(int32_t *cc, int *percentage) {\n   if (ttype != CSS_TK_DECINT) {\n      MSG_CSS(\"expected integer not found in %s color\\n\", \"rgb\");\n      return false;\n   }\n\n   *cc = strtol(tval, NULL, 10);\n\n   nextToken();\n   if (ttype == CSS_TK_CHAR && tval[0] == '%') {\n      if (*percentage == 0) {\n         MSG_CSS(\"'%s' unexpected in rgb color\\n\", \"%\");\n         return false;\n      }\n      *percentage = 1;\n      *cc = *cc * 255 / 100;\n      nextToken();\n   } else {\n      if (*percentage == 1) {\n         MSG_CSS(\"expected '%s' not found in rgb color\\n\", \"%\");\n         return false;\n      }\n      *percentage = 0;\n   }\n\n   if (*cc > 255)\n      *cc = 255;\n   if (*cc < 0)\n      *cc = 0;\n\n   return true;\n}\n\nbool CssParser::parseRgbColor(int32_t *c) {\n   int32_t cc;\n   int percentage = -1;\n\n   *c = 0;\n\n   if (ttype != CSS_TK_CHAR || tval[0] != '(') {\n      MSG_CSS(\"expected '%s' not found in rgb color\\n\", \"(\");\n      return false;\n   }\n   nextToken();\n\n   if (!parseRgbColorComponent(&cc, &percentage))\n      return false;\n   *c |= cc << 16;\n\n   if (ttype != CSS_TK_CHAR || tval[0] != ',') {\n      MSG_CSS(\"expected '%s' not found in rgb color\\n\", \",\");\n      return false;\n   }\n   nextToken();\n\n   if (!parseRgbColorComponent(&cc, &percentage))\n      return false;\n   *c |= cc << 8;\n\n   if (ttype != CSS_TK_CHAR || tval[0] != ',') {\n      MSG_CSS(\"expected '%s' not found in rgb color\\n\", \",\");\n      return false;\n   }\n   nextToken();\n\n   if (!parseRgbColorComponent(&cc, &percentage))\n      return false;\n   *c |= cc;\n\n   if (ttype != CSS_TK_CHAR || tval[0] != ')') {\n      MSG_CSS(\"expected '%s' not found in rgb color\\n\", \")\");\n      return false;\n   }\n\n   return true;\n}\n\nbool CssParser::parseValue(CssPropertyName prop,\n                           CssValueType type,\n                           CssPropertyValue *val)\n{\n   CssLengthType lentype;\n   bool found, ret = false;\n   float fval;\n   int i, ival, err = 1;\n   Dstr *dstr;\n\n   switch (type) {\n   case CSS_TYPE_ENUM:\n      if (ttype == CSS_TK_SYMBOL) {\n         for (i = 0; Css_property_info[prop].enum_symbols[i]; i++)\n            if (dStrAsciiCasecmp(tval,\n                            Css_property_info[prop].enum_symbols[i]) == 0) {\n               val->intVal = i;\n               ret = true;\n               break;\n            }\n         nextToken();\n      }\n      break;\n\n   case CSS_TYPE_MULTI_ENUM:\n      val->intVal = 0;\n      ret = true;\n\n      while (ttype == CSS_TK_SYMBOL) {\n         if (dStrAsciiCasecmp(tval, \"none\") != 0) {\n            for (i = 0, found = false;\n                 !found && Css_property_info[prop].enum_symbols[i]; i++) {\n               if (dStrAsciiCasecmp(tval,\n                               Css_property_info[prop].enum_symbols[i]) == 0)\n                  val->intVal |= (1 << i);\n            }\n         }\n         nextToken();\n      }\n      break;\n\n   case CSS_TYPE_LENGTH_PERCENTAGE:\n   case CSS_TYPE_LENGTH_PERCENTAGE_NUMBER:\n   case CSS_TYPE_LENGTH:\n   case CSS_TYPE_SIGNED_LENGTH:\n      if (ttype == CSS_TK_DECINT || ttype == CSS_TK_FLOAT) {\n         fval = atof(tval);\n         lentype = CSS_LENGTH_TYPE_NONE;\n\n         nextToken();\n         if (!spaceSeparated && ttype == CSS_TK_SYMBOL) {\n            ret = true;\n\n            if (dStrAsciiCasecmp(tval, \"px\") == 0) {\n               lentype = CSS_LENGTH_TYPE_PX;\n               nextToken();\n            } else if (dStrAsciiCasecmp(tval, \"mm\") == 0) {\n               lentype = CSS_LENGTH_TYPE_MM;\n               nextToken();\n            } else if (dStrAsciiCasecmp(tval, \"cm\") == 0) {\n               lentype = CSS_LENGTH_TYPE_MM;\n               fval *= 10;\n               nextToken();\n            } else if (dStrAsciiCasecmp(tval, \"in\") == 0) {\n               lentype = CSS_LENGTH_TYPE_MM;\n               fval *= 25.4;\n               nextToken();\n            } else if (dStrAsciiCasecmp(tval, \"pt\") == 0) {\n               lentype = CSS_LENGTH_TYPE_MM;\n               fval *= (25.4 / 72);\n               nextToken();\n            } else if (dStrAsciiCasecmp(tval, \"pc\") == 0) {\n               lentype = CSS_LENGTH_TYPE_MM;\n               fval *= (25.4 / 6);\n               nextToken();\n            } else if (dStrAsciiCasecmp(tval, \"em\") == 0) {\n               lentype = CSS_LENGTH_TYPE_EM;\n               nextToken();\n            } else if (dStrAsciiCasecmp(tval, \"ex\") == 0) {\n               lentype = CSS_LENGTH_TYPE_EX;\n               nextToken();\n            } else if (dStrAsciiCasecmp(tval, \"ch\") == 0) {\n               lentype = CSS_LENGTH_TYPE_EM;\n               nextToken();\n            } else {\n               ret = false;\n            }\n         } else if (!spaceSeparated &&\n                    (type == CSS_TYPE_LENGTH_PERCENTAGE ||\n                     type == CSS_TYPE_LENGTH_PERCENTAGE_NUMBER) &&\n                    ttype == CSS_TK_CHAR &&\n                    tval[0] == '%') {\n            fval /= 100;\n            lentype = CSS_LENGTH_TYPE_PERCENTAGE;\n            ret = true;\n            nextToken();\n         }\n\n         /* Allow numbers without unit only for 0 or\n          * CSS_TYPE_LENGTH_PERCENTAGE_NUMBER\n          */\n         if (lentype == CSS_LENGTH_TYPE_NONE &&\n            (type == CSS_TYPE_LENGTH_PERCENTAGE_NUMBER || fval == 0.0))\n            ret = true;\n\n         val->intVal = CSS_CREATE_LENGTH(fval, lentype);\n      }\n      break;\n\n   case CSS_TYPE_AUTO:\n      assert (ttype == CSS_TK_SYMBOL && !dStrAsciiCasecmp(tval, \"auto\"));\n      ret = true;\n      val->intVal = CSS_LENGTH_TYPE_AUTO;\n      nextToken();\n      break;\n\n   case CSS_TYPE_COLOR:\n      if (ttype == CSS_TK_COLOR) {\n         val->intVal = a_Color_parse(tval, -1, &err);\n         if (err)\n            MSG_CSS(\"color is not in \\\"%s\\\" format\\n\", \"#RRGGBB\");\n         else\n            ret = true;\n         nextToken();\n      } else if (ttype == CSS_TK_SYMBOL) {\n         if (dStrAsciiCasecmp(tval, \"rgb\") == 0) {\n            nextToken();\n            if (parseRgbColor(&val->intVal))\n               ret = true;\n            else\n               MSG_CSS(\"Failed to parse %s color\\n\", \"rgb(r,g,b)\");\n         } else {\n            val->intVal = a_Color_parse(tval, -1, &err);\n            if (err)\n               MSG_CSS(\"color is not in \\\"%s\\\" format\\n\", \"#RRGGBB\");\n            else\n               ret = true;\n         }\n         nextToken();\n      }\n      break;\n\n   case CSS_TYPE_STRING:\n      if (ttype == CSS_TK_STRING) {\n         val->strVal = dStrdup(tval);\n         ret = true;\n         nextToken();\n      }\n      break;\n\n   case CSS_TYPE_SYMBOL:\n      /* Read comma separated list of font family names */\n      dstr = dStr_new(\"\");\n      while (ttype == CSS_TK_SYMBOL || ttype == CSS_TK_STRING ||\n             (ttype == CSS_TK_CHAR && tval[0] == ',')) {\n         if (spaceSeparated)\n            dStr_append_c(dstr, ' ');\n         dStr_append(dstr, tval);\n         ret = true;\n         nextToken();\n      }\n\n      if (ret) {\n         val->strVal = dStrstrip(dstr->str);\n         dStr_free(dstr, 0);\n      } else {\n         dStr_free(dstr, 1);\n      }\n      break;\n\n   case CSS_TYPE_FONT_WEIGHT:\n      ival = 0;\n      if (ttype == CSS_TK_DECINT) {\n         ival = strtol(tval, NULL, 10);\n         if (ival < 100 || ival > 900)\n            /* invalid */\n            ival = 0;\n      }\n\n      if (ival != 0) {\n         val->intVal = ival;\n         ret = true;\n         nextToken();\n      }\n      break;\n\n   case CSS_TYPE_URI:\n      if (ttype == CSS_TK_SYMBOL) {\n         if (dStrAsciiCasecmp(tval, \"url\") == 0) {\n            val->strVal = parseUrl();\n            if (val->strVal)\n               ret = true;\n         } else if (dStrAsciiCasecmp(tval, \"none\") == 0) {\n            val->strVal = NULL;\n            ret = true;\n         }\n         nextToken();\n      }\n      break;\n\n   case CSS_TYPE_BACKGROUND_POSITION:\n      // 'background-position' consists of one or two values: vertical and\n      // horizontal position; in most cases in this order. However, as long it\n      // is unambigous, the order can be switched: \"10px left\" and \"left 10px\"\n      // are both possible and have the same effect. For this reason, all\n      // possibilities are tested in parallel.\n\n      bool h[2], v[2];\n      int pos[2];\n      h[0] = v[0] = h[1] = v[1] = false;\n\n      // First: collect values in pos[0] and pos[1], and determine whether\n      // they can be used for a horizontal (h[i]) or vertical (v[i]) position\n      // (or both). When neither h[i] or v[i] is set, pos[i] is undefined.\n      for (i = 0; i < 2; i++) {\n         CssValueType typeTmp;\n         // tokenMatchesProperty will, for CSS_PROPERTY_BACKGROUND_POSITION,\n         // work on both parts, since they are exchangable.\n         if (tokenMatchesProperty (CSS_PROPERTY_BACKGROUND_POSITION,\n                                   &typeTmp)) {\n            h[i] = ttype != CSS_TK_SYMBOL ||\n               (dStrAsciiCasecmp(tval, \"top\") != 0 &&\n                dStrAsciiCasecmp(tval, \"bottom\") != 0);\n            v[i] = ttype != CSS_TK_SYMBOL ||\n               (dStrAsciiCasecmp(tval, \"left\") != 0 &&\n                dStrAsciiCasecmp(tval, \"right\") != 0);\n         } else\n            // No match.\n            h[i] = v[i] = false;\n\n         if (h[i] || v[i]) {\n            // Calculate values.\n            if (ttype == CSS_TK_SYMBOL) {\n               if (dStrAsciiCasecmp(tval, \"top\") == 0 ||\n                   dStrAsciiCasecmp(tval, \"left\") == 0) {\n                  pos[i] = CSS_CREATE_LENGTH (0.0, CSS_LENGTH_TYPE_PERCENTAGE);\n                  nextToken();\n               } else if (dStrAsciiCasecmp(tval, \"center\") == 0) {\n                  pos[i] = CSS_CREATE_LENGTH (0.5, CSS_LENGTH_TYPE_PERCENTAGE);\n                  nextToken();\n               } else if (dStrAsciiCasecmp(tval, \"bottom\") == 0 ||\n                          dStrAsciiCasecmp(tval, \"right\") == 0) {\n                  pos[i] = CSS_CREATE_LENGTH (1.0, CSS_LENGTH_TYPE_PERCENTAGE);\n                  nextToken();\n               } else\n                  // tokenMatchesProperty should have returned \"false\" already.\n                  lout::misc::assertNotReached ();\n            } else {\n               // We can assume <length> or <percentage> here ...\n               CssPropertyValue valTmp;\n               if (parseValue(prop, CSS_TYPE_LENGTH_PERCENTAGE, &valTmp)) {\n                  pos[i] = valTmp.intVal;\n                  ret = true;\n               } else if (parseValue(prop, CSS_TYPE_SIGNED_LENGTH, &valTmp)) {\n                  pos[i] = valTmp.intVal;\n                  ret = true;\n               } else\n                  // ... but something may still fail.\n                  h[i] = v[i] = false;\n            }\n         }\n\n         // If the first value cannot be read, do not read the second.\n         if (!h[i] && !v[i])\n            break;\n      }\n\n      // Second: Create the final value. Order will be determined here.\n      if (v[0] || h[0]) {\n         // If second value is not set, it is set to \"center\", i. e. 50%, (see\n         // CSS specification), which is suitable for both dimensions.\n         if (!h[1] && !v[1]) {\n            pos[1] = CSS_CREATE_LENGTH (0.5, CSS_LENGTH_TYPE_PERCENTAGE);\n            h[1] = v[1] = true;\n         }\n\n         // Only valid, when a combination h/v or v/h is possible.\n         if ((h[0] && v[1]) || (v[0] && h[1])) {\n            ret = true;\n            val->posVal = dNew(CssBackgroundPosition, 1);\n\n            // Prefer combination h/v:\n            if (h[0] && v[1]) {\n                val->posVal->posX = pos[0];\n                val->posVal->posY = pos[1];\n            } else {\n               // This should be v/h:\n                val->posVal->posX = pos[1];\n                val->posVal->posY = pos[0];\n            }\n         }\n      }\n      break;\n\n   case CSS_TYPE_INTEGER:\n      if (ttype == CSS_TK_DECINT) {\n         val->intVal = strtol(tval, NULL, 10);\n         ret = true;\n         nextToken();\n      }\n      break;\n\n   case CSS_TYPE_UNUSED:\n      /* nothing */\n      break;\n\n   default:\n      assert(false);            /* not reached */\n   }\n\n   return ret;\n}\n\nbool CssParser::parseWeight()\n{\n   if (ttype == CSS_TK_CHAR && tval[0] == '!') {\n      nextToken();\n      if (ttype == CSS_TK_SYMBOL &&\n          dStrAsciiCasecmp(tval, \"important\") == 0) {\n         nextToken();\n         return true;\n      }\n   }\n\n   return false;\n}\n\n/*\n * bsearch(3) compare function for searching properties\n */\nstatic int Css_property_info_cmp(const void *a, const void *b)\n{\n   return dStrAsciiCasecmp(((CssPropertyInfo *) a)->symbol,\n                      ((CssPropertyInfo *) b)->symbol);\n}\n\n\n/*\n * bsearch(3) compare function for searching shorthands\n */\nstatic int Css_shorthand_info_cmp(const void *a, const void *b)\n{\n   return dStrAsciiCasecmp(((CssShorthandInfo *) a)->symbol,\n                      ((CssShorthandInfo *) b)->symbol);\n}\n\n/*\n * excludes some properties that may break reader mode\n */\nstatic int safeCss(CssPropertyName prop) {\n   switch(prop) {\n      case CSS_PROPERTY_DISPLAY:\n      case CSS_PROPERTY_FLOAT:\n      case CSS_PROPERTY_MARGIN_BOTTOM:\n      case CSS_PROPERTY_MARGIN_LEFT:\n      case CSS_PROPERTY_MARGIN_RIGHT:\n      case CSS_PROPERTY_MARGIN_TOP:\n      case CSS_PROPERTY_MAX_HEIGHT:\n      case CSS_PROPERTY_MAX_WIDTH:\n      case CSS_PROPERTY_MIN_HEIGHT:\n      case CSS_PROPERTY_MIN_WIDTH:\n      case CSS_PROPERTY_WIDTH:\n         return 0;\n      default:\n         return 1;\n   }\n}\n\nvoid CssParser::parseDeclaration(CssPropertyList *props,\n                                 CssPropertyList *importantProps)\n{\n   CssPropertyInfo pi = {NULL, {CSS_TYPE_UNUSED}, NULL}, *pip;\n   CssShorthandInfo *sip;\n   CssValueType type = CSS_TYPE_UNUSED;\n\n   CssPropertyName prop;\n   CssPropertyValue val, dir_vals[4];\n   CssValueType dir_types[4];\n   bool found, weight;\n   int sh_index, i, j, n;\n   int dir_set[4][4] = {\n      /* 1 value  */ {0, 0, 0, 0},\n      /* 2 values */ {0, 0, 1, 1},\n      /* 3 values */ {0, 2, 1, 1},\n      /* 4 values */ {0, 2, 3, 1}\n   };\n\n   if (ttype == CSS_TK_SYMBOL) {\n      pi.symbol = tval;\n      pip =\n          (CssPropertyInfo *) bsearch(&pi, Css_property_info,\n                                      CSS_NUM_PARSED_PROPERTIES,\n                                      sizeof(CssPropertyInfo),\n                                      Css_property_info_cmp);\n      if (pip) {\n         prop = (CssPropertyName) (pip - Css_property_info);\n         nextToken();\n         if (ttype == CSS_TK_CHAR && tval[0] == ':') {\n            nextToken();\n            if (tokenMatchesProperty (prop, &type) &&\n                parseValue(prop, type, &val)) {\n               weight = parseWeight();\n               if(!prefs.load_reader_mode_css ||\n                  origin != CSS_ORIGIN_AUTHOR ||\n                  safeCss(prop)) { // fix reader mode on some sites\n                  if (weight && importantProps)\n                     importantProps->set(prop, type, val);\n                  else\n                     props->set(prop, type, val);\n               }\n            }\n         }\n      } else {\n         /* Try shorthands. */\n         sip =\n             (CssShorthandInfo *) bsearch(&pi, Css_shorthand_info,\n                                          CSS_SHORTHAND_NUM,\n                                          sizeof(CssShorthandInfo),\n                                          Css_shorthand_info_cmp);\n         if (sip) {\n            sh_index = sip - Css_shorthand_info;\n            nextToken();\n            if (ttype == CSS_TK_CHAR && tval[0] == ':') {\n               nextToken();\n\n               switch (Css_shorthand_info[sh_index].type) {\n\n               case CssShorthandInfo::CSS_SHORTHAND_FONT:\n                  /* \\todo Implement details. */\n               case CssShorthandInfo::CSS_SHORTHAND_MULTIPLE:\n                  do {\n                     for (found = false, i = 0;\n                          !found &&\n                          Css_shorthand_info[sh_index].properties[i] !=\n                          CSS_PROPERTY_END;\n                          i++)\n                        if (tokenMatchesProperty(Css_shorthand_info[sh_index].\n                                                 properties[i], &type)) {\n                           found = true;\n                           DEBUG_MSG(DEBUG_PARSE_LEVEL,\n                                     \"will assign to '%s'\\n\",\n                                     Css_property_info\n                                     [Css_shorthand_info[sh_index]\n                                      .properties[i]].symbol);\n                           if (parseValue(Css_shorthand_info[sh_index]\n                                          .properties[i], type, &val)) {\n                              weight = parseWeight();\n                              if(!prefs.load_reader_mode_css ||\n                                 origin != CSS_ORIGIN_AUTHOR ||\n                                 safeCss(Css_shorthand_info[sh_index].\n                                         properties[i])) { // fix reader mode on some sites\n                                 if (weight && importantProps)\n                                    importantProps->\n                                        set(Css_shorthand_info[sh_index].\n                                            properties[i], type, val);\n                                 else\n                                    props->set(Css_shorthand_info[sh_index].\n                                               properties[i], type, val);\n                              }\n                           }\n                        }\n                  } while (found);\n                  break;\n\n               case CssShorthandInfo::CSS_SHORTHAND_DIRECTIONS:\n                  n = 0;\n                  while (n < 4) {\n                     if (tokenMatchesProperty(Css_shorthand_info[sh_index].\n                                              properties[0], &type) &&\n                         parseValue(Css_shorthand_info[sh_index]\n                                    .properties[0], type, &val)) {\n                        dir_vals[n] = val;\n                        dir_types[n] = type;\n                        n++;\n                     } else\n                        break;\n                  }\n\n                  weight = parseWeight();\n                  if (n > 0) {\n                     for (i = 0; i < 4; i++)\n                        if(!prefs.load_reader_mode_css ||\n                           origin != CSS_ORIGIN_AUTHOR ||\n                           safeCss(Css_shorthand_info[sh_index]\n                                    .properties[i])) { // fix reader mode on some sites\n                           if (weight && importantProps)\n                              importantProps->set(Css_shorthand_info[sh_index]\n                                                  .properties[i],\n                                                  dir_types[dir_set[n - 1][i]],\n                                                  dir_vals[dir_set[n - 1][i]]);\n                           else\n                              props->set(Css_shorthand_info[sh_index]\n                                         .properties[i],\n                                         dir_types[dir_set[n - 1][i]],\n                                         dir_vals[dir_set[n - 1][i]]);\n                        }\n                  } else\n                     MSG_CSS(\"no values for shorthand property '%s'\\n\",\n                             Css_shorthand_info[sh_index].symbol);\n\n                  break;\n\n               case CssShorthandInfo::CSS_SHORTHAND_BORDER:\n                  do {\n                     for (found = false, i = 0;\n                          !found && i < 3;\n                          i++)\n                        if (tokenMatchesProperty(Css_shorthand_info[sh_index].\n                                                 properties[i], &type)) {\n                           found = true;\n                           if (parseValue(Css_shorthand_info[sh_index]\n                                          .properties[i], type, &val)) {\n                              weight = parseWeight();\n                              for (j = 0; j < 4; j++)\n                                 if(!prefs.load_reader_mode_css ||\n                                    origin != CSS_ORIGIN_AUTHOR ||\n                                    safeCss(Css_shorthand_info[sh_index]\n                                             .properties[i])) { // fix reader mode on some sites\n                                    if (weight && importantProps)\n                                       importantProps->\n                                          set(Css_shorthand_info[sh_index].\n                                             properties[j * 3 + i], type, val);\n                                    else\n                                       props->set(Css_shorthand_info[sh_index].\n                                          properties[j * 3 + i], type, val);\n                                 }\n                           }\n                        }\n                  } while (found);\n                  break;\n               }\n            }\n         }\n      }\n   }\n\n   /* Skip all tokens until the expected end. */\n   while (!(ttype == CSS_TK_END ||\n            (ttype == CSS_TK_CHAR &&\n             (tval[0] == ';' || tval[0] == '}'))))\n      nextToken();\n\n   if (ttype == CSS_TK_CHAR && tval[0] == ';')\n      nextToken();\n}\n\nbool CssParser::parseSimpleSelector(CssSimpleSelector *selector)\n{\n   CssSimpleSelector::SelectType selectType;\n\n   if (ttype == CSS_TK_SYMBOL) {\n      selector->setElement (a_Html_tag_index(tval));\n      nextToken();\n      if (spaceSeparated)\n         return true;\n   } else if (ttype == CSS_TK_CHAR && tval[0] == '*') {\n      selector->setElement (CssSimpleSelector::ELEMENT_ANY);\n      nextToken();\n      if (spaceSeparated)\n         return true;\n   } else if (ttype == CSS_TK_CHAR &&\n              (tval[0] == '#' ||\n               tval[0] == '.' ||\n               tval[0] == ':')) {\n      // nothing to be done in this case\n   } else {\n      return false;\n   }\n\n   do {\n      selectType = CssSimpleSelector::SELECT_NONE;\n      if (ttype == CSS_TK_CHAR) {\n         switch (tval[0]) {\n         case '#':\n            selectType = CssSimpleSelector::SELECT_ID;\n            break;\n         case '.':\n            selectType = CssSimpleSelector::SELECT_CLASS;\n            break;\n         case ':':\n            selectType = CssSimpleSelector::SELECT_PSEUDO_CLASS;\n            if (selector->getPseudoClass ())\n               // pseudo class has been set already.\n               // As dillo currently only supports :link and :visisted, a\n               // selector with more than one pseudo class will never match.\n               // By returning false, the whole CssRule will be dropped.\n               // \\todo adapt this when supporting :hover, :active...\n               return false;\n            break;\n         }\n      }\n\n      if (selectType != CssSimpleSelector::SELECT_NONE) {\n         nextToken();\n         if (spaceSeparated)\n            return false;\n\n         if (ttype == CSS_TK_SYMBOL) {\n            selector->setSelect (selectType, tval);\n            nextToken();\n         } else {\n            return false; // don't accept classes or id's starting with integer\n         }\n         if (spaceSeparated)\n            return true;\n      }\n   } while (selectType != CssSimpleSelector::SELECT_NONE);\n\n   DEBUG_MSG(DEBUG_PARSE_LEVEL, \"end of simple selector (%s, %s, %s, %d)\\n\",\n      selector->id, selector->klass,\n      selector->pseudo, selector->element);\n\n   return true;\n}\n\nCssSelector *CssParser::parseSelector()\n{\n   CssSelector *selector = new CssSelector ();\n\n   while (true) {\n      if (! parseSimpleSelector (selector->top ())) {\n         delete selector;\n         selector = NULL;\n         break;\n      }\n\n      if (ttype == CSS_TK_CHAR &&\n         (tval[0] == ',' || tval[0] == '{')) {\n         break;\n      } else if (ttype == CSS_TK_CHAR && tval[0] == '>') {\n         selector->addSimpleSelector (CssSelector::COMB_CHILD);\n         nextToken();\n      } else if (ttype == CSS_TK_CHAR && tval[0] == '+') {\n         selector->addSimpleSelector (CssSelector::COMB_ADJACENT_SIBLING);\n         nextToken();\n      } else if (ttype != CSS_TK_END && spaceSeparated) {\n         selector->addSimpleSelector (CssSelector::COMB_DESCENDANT);\n      } else {\n         delete selector;\n         selector = NULL;\n         break;\n      }\n   }\n\n   while (ttype != CSS_TK_END &&\n          (ttype != CSS_TK_CHAR ||\n           (tval[0] != ',' && tval[0] != '{')))\n         nextToken();\n\n   return selector;\n}\n\nvoid CssParser::parseRuleset()\n{\n   lout::misc::SimpleVector < CssSelector * >*list;\n   CssPropertyList *props, *importantProps;\n   CssSelector *selector;\n\n   list = new lout::misc::SimpleVector < CssSelector * >(1);\n\n   while (true) {\n      selector = parseSelector();\n\n      if (selector) {\n         selector->ref();\n         list->increase();\n         list->set(list->size() - 1, selector);\n      }\n\n      // \\todo dump whole ruleset in case of parse error as required by CSS 2.1\n      //       however make sure we don't dump it if only dillo fails to parse\n      //       valid CSS.\n\n      if (ttype == CSS_TK_CHAR && tval[0] == ',')\n         /* To read the next token. */\n         nextToken();\n      else\n         /* No more selectors. */\n         break;\n   }\n\n   DEBUG_MSG(DEBUG_PARSE_LEVEL, \"end of %s\\n\", \"selectors\");\n\n   props = new CssPropertyList(true);\n   props->ref();\n   importantProps = new CssPropertyList(true);\n   importantProps->ref();\n\n   /* Read block. ('{' has already been read.) */\n   if (ttype != CSS_TK_END) {\n      withinBlock = true;\n      nextToken();\n      do\n         parseDeclaration(props, importantProps);\n      while (!(ttype == CSS_TK_END ||\n               (ttype == CSS_TK_CHAR && tval[0] == '}')));\n      withinBlock = false;\n   }\n\n   for (int i = 0; i < list->size(); i++) {\n      CssSelector *s = list->get(i);\n\n      if (origin == CSS_ORIGIN_USER_AGENT) {\n         context->addRule(s, props, CSS_PRIMARY_USER_AGENT);\n      } else if (origin == CSS_ORIGIN_USER) {\n         context->addRule(s, props, CSS_PRIMARY_USER);\n         context->addRule(s, importantProps, CSS_PRIMARY_USER_IMPORTANT);\n      } else if (origin == CSS_ORIGIN_AUTHOR) {\n         context->addRule(s, props, CSS_PRIMARY_AUTHOR);\n         context->addRule(s, importantProps, CSS_PRIMARY_AUTHOR_IMPORTANT);\n      }\n\n      s->unref();\n   }\n\n   props->unref();\n   importantProps->unref();\n\n   delete list;\n\n   if (ttype == CSS_TK_CHAR && tval[0] == '}')\n      nextToken();\n}\n\nchar * CssParser::parseUrl()\n{\n   Dstr *urlStr = NULL;\n\n   if (ttype != CSS_TK_SYMBOL ||\n      dStrAsciiCasecmp(tval, \"url\") != 0)\n      return NULL;\n\n   nextToken();\n\n   if (ttype != CSS_TK_CHAR || tval[0] != '(')\n      return NULL;\n\n   nextToken();\n\n   if (ttype == CSS_TK_STRING) {\n      urlStr = dStr_new(tval);\n      nextToken();\n   } else {\n      urlStr = dStr_new(\"\");\n      while (ttype != CSS_TK_END &&\n             (ttype != CSS_TK_CHAR || tval[0] != ')')) {\n         dStr_append(urlStr, tval);\n         nextToken();\n      }\n   }\n\n   if (ttype != CSS_TK_CHAR || tval[0] != ')') {\n      dStr_free(urlStr, 1);\n      urlStr = NULL;\n   }\n\n   if (urlStr) {\n      DilloUrl *dilloUrl = a_Url_new(urlStr->str, a_Url_str(this->baseUrl));\n      char *url = dStrdup(a_Url_str(dilloUrl));\n      a_Url_free(dilloUrl);\n      dStr_free(urlStr, 1);\n      return url;\n   } else {\n      return NULL;\n   }\n}\n\nvoid CssParser::parseImport(DilloHtml *html)\n{\n   char *urlStr = NULL;\n   bool importSyntaxIsOK = false;\n   bool mediaSyntaxIsOK = true;\n   bool mediaIsSelected = true;\n\n   nextToken();\n\n   if (ttype == CSS_TK_SYMBOL &&\n       dStrAsciiCasecmp(tval, \"url\") == 0)\n      urlStr = parseUrl();\n   else if (ttype == CSS_TK_STRING)\n      urlStr = dStrdup (tval);\n\n   nextToken();\n\n   /* parse a comma-separated list of media */\n   if (ttype == CSS_TK_SYMBOL) {\n      mediaSyntaxIsOK = false;\n      mediaIsSelected = false;\n      while (ttype == CSS_TK_SYMBOL) {\n         if (dStrAsciiCasecmp(tval, \"all\") == 0 ||\n             dStrAsciiCasecmp(tval, \"screen\") == 0)\n            mediaIsSelected = true;\n         nextToken();\n         if (ttype == CSS_TK_CHAR && tval[0] == ',') {\n            nextToken();\n         } else {\n            mediaSyntaxIsOK = true;\n            break;\n         }\n      }\n   }\n\n   if (mediaSyntaxIsOK &&\n       ttype == CSS_TK_CHAR &&\n       tval[0] == ';') {\n      importSyntaxIsOK = true;\n      nextToken();\n   } else\n      ignoreStatement();\n\n   if (urlStr) {\n      if (importSyntaxIsOK && mediaIsSelected) {\n         MSG(\"CssParser::parseImport(): @import %s\\n\", urlStr);\n         DilloUrl *url = a_Html_url_new (html, urlStr, a_Url_str(this->baseUrl),\n                                         this->baseUrl ? 1 : 0);\n         a_Html_load_stylesheet(html, url);\n         a_Url_free(url);\n      }\n      dFree (urlStr);\n   }\n}\n\nvoid CssParser::parseMedia()\n{\n   bool mediaSyntaxIsOK = false;\n   bool mediaIsSelected = false;\n\n   nextToken();\n\n   /* parse a comma-separated list of media */\n   while (ttype == CSS_TK_SYMBOL) {\n      if (dStrAsciiCasecmp(tval, \"all\") == 0 ||\n          dStrAsciiCasecmp(tval, \"screen\") == 0)\n         mediaIsSelected = true;\n      nextToken();\n      if (ttype == CSS_TK_CHAR && tval[0] == ',') {\n         nextToken();\n      } else {\n         mediaSyntaxIsOK = true;\n         break;\n      }\n   }\n\n   /* check that the syntax is OK so far */\n   if (!(mediaSyntaxIsOK &&\n         ttype == CSS_TK_CHAR &&\n         tval[0] == '{')) {\n      ignoreStatement();\n      return;\n   }\n\n   /* parse/ignore the block as required */\n   if (mediaIsSelected) {\n      nextToken();\n      while (ttype != CSS_TK_END) {\n         parseRuleset();\n         if (ttype == CSS_TK_CHAR && tval[0] == '}') {\n            nextToken();\n            break;\n         }\n      }\n   } else\n      ignoreBlock();\n}\n\nconst char * CssParser::propertyNameString(CssPropertyName name)\n{\n   return Css_property_info[name].symbol;\n}\n\nvoid CssParser::ignoreBlock()\n{\n   int depth = 0;\n\n   while (ttype != CSS_TK_END) {\n      if (ttype == CSS_TK_CHAR) {\n         if (tval[0] == '{') {\n            depth++;\n         } else if (tval[0] == '}') {\n            depth--;\n            if (depth == 0) {\n               nextToken();\n               return;\n            }\n         }\n      }\n      nextToken();\n   }\n}\n\nvoid CssParser::ignoreStatement()\n{\n   while (ttype != CSS_TK_END) {\n      if (ttype == CSS_TK_CHAR) {\n         if (tval[0] == ';') {\n            nextToken();\n            return;\n         } else if (tval[0] =='{') {\n            ignoreBlock();\n            return;\n         }\n      }\n      nextToken();\n   }\n}\n\nvoid CssParser::parse(DilloHtml *html, const DilloUrl *baseUrl,\n                      CssContext *context,\n                      const char *buf,\n                      int buflen, CssOrigin origin)\n{\n   CssParser parser (context, origin, baseUrl, buf, buflen);\n   bool importsAreAllowed = true;\n\n   while (parser.ttype != CSS_TK_END) {\n      if (parser.ttype == CSS_TK_CHAR &&\n          parser.tval[0] == '@') {\n         parser.nextToken();\n         if (parser.ttype == CSS_TK_SYMBOL) {\n            if (dStrAsciiCasecmp(parser.tval, \"import\") == 0 &&\n                html != NULL &&\n                importsAreAllowed) {\n               parser.parseImport(html);\n            } else if (dStrAsciiCasecmp(parser.tval, \"media\") == 0) {\n               parser.parseMedia();\n            } else {\n               parser.ignoreStatement();\n            }\n         } else {\n            parser.ignoreStatement();\n         }\n      } else {\n         importsAreAllowed = false;\n         parser.parseRuleset();\n      }\n   }\n}\n\nvoid CssParser::parseDeclarationBlock(const DilloUrl *baseUrl,\n                                      const char *buf, int buflen,\n                                      CssPropertyList *props,\n                                      CssPropertyList *propsImportant)\n{\n   CssParser parser (NULL, CSS_ORIGIN_AUTHOR, baseUrl, buf, buflen);\n\n   parser.withinBlock = true;\n\n   do\n      parser.parseDeclaration(props, propsImportant);\n   while (!(parser.ttype == CSS_TK_END ||\n         (parser.ttype == CSS_TK_CHAR && parser.tval[0] == '}')));\n}\n"
  },
  {
    "path": "src/cssparser.hh",
    "content": "#ifndef __CSSPARSER_HH__\n#define __CSSPARSER_HH__\n\n#include \"css.hh\"\n\nclass DilloHtml;\n\nclass CssParser {\n   private:\n      typedef enum {\n         CSS_TK_DECINT, CSS_TK_FLOAT, CSS_TK_COLOR, CSS_TK_SYMBOL,\n         CSS_TK_STRING, CSS_TK_CHAR, CSS_TK_END\n      } CssTokenType;\n\n      static const int maxStrLen = 256;\n      CssContext *context;\n      CssOrigin origin;\n      const DilloUrl *baseUrl;\n\n      const char *buf;\n      int buflen, bufptr;\n\n      CssTokenType ttype;\n      char tval[maxStrLen];\n      bool withinBlock;\n      bool spaceSeparated; /* used when parsing CSS selectors */\n\n      CssParser(CssContext *context, CssOrigin origin, const DilloUrl *baseUrl,\n                const char *buf, int buflen);\n      int getChar();\n      void ungetChar();\n      void nextToken();\n      bool skipString(int c, const char *string);\n      bool tokenMatchesProperty(CssPropertyName prop, CssValueType * type);\n      bool parseValue(CssPropertyName prop, CssValueType type,\n                      CssPropertyValue * val);\n      bool parseWeight();\n      bool parseRgbColorComponent(int32_t *cc, int *percentage);\n      bool parseRgbColor(int32_t *c);\n      void parseDeclaration(CssPropertyList * props,\n                            CssPropertyList * importantProps);\n      bool parseSimpleSelector(CssSimpleSelector *selector);\n      char *parseUrl();\n      void parseImport(DilloHtml *html);\n      void parseMedia();\n      CssSelector *parseSelector();\n      void parseRuleset();\n      void ignoreBlock();\n      void ignoreStatement();\n\n   public:\n      static void parseDeclarationBlock(const DilloUrl *baseUrl,\n                                        const char *buf, int buflen,\n                                        CssPropertyList *props,\n                                        CssPropertyList *propsImortant);\n      static void parse(DilloHtml *html, const DilloUrl *baseUrl, CssContext *context,\n                        const char *buf, int buflen, CssOrigin origin);\n      static const char *propertyNameString(CssPropertyName name);\n};\n\n#endif\n"
  },
  {
    "path": "src/decode.c",
    "content": "/*\n * File: decode.c\n *\n * Copyright 2007-2008 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <zlib.h>\n#include <iconv.h>\n#include <errno.h>\n#include <stdlib.h>     /* strtol */\n\n#include \"decode.h\"\n#include \"utf8.hh\"\n#include \"msg.h\"\n\nstatic const int bufsize = 8*1024;\n\n/*\n * Decode 'Transfer-Encoding: chunked' data\n */\nDstr *a_Decode_transfer_process(DecodeTransfer *dc, const char *instr,\n                                int inlen)\n{\n   char *inputPtr, *eol;\n   int inputRemaining;\n   int chunkRemaining = *((int *)dc->state);\n   Dstr *output = dStr_sized_new(inlen);\n\n   dStr_append_l(dc->leftover, instr, inlen);\n   inputPtr = dc->leftover->str;\n   inputRemaining = dc->leftover->len;\n\n   while (inputRemaining > 0) {\n      if (chunkRemaining > 2) {\n         /* chunk body to copy */\n         int copylen = MIN(chunkRemaining - 2, inputRemaining);\n         dStr_append_l(output, inputPtr, copylen);\n         chunkRemaining -= copylen;\n         inputRemaining -= copylen;\n         inputPtr += copylen;\n      }\n\n      if ((chunkRemaining == 2) && (inputRemaining > 0)) {\n         /* CR to discard */\n         chunkRemaining--;\n         inputRemaining--;\n         inputPtr++;\n      }\n      if ((chunkRemaining == 1) && (inputRemaining > 0)) {\n         /* LF to discard */\n         chunkRemaining--;\n         inputRemaining--;\n         inputPtr++;\n      }\n\n      /*\n       * A chunk has a one-line header that begins with the chunk length\n       * in hexadecimal.\n       */\n      if (!(eol = (char *)memchr(inputPtr, '\\n', inputRemaining))) {\n         break;   /* We don't have the whole line yet. */\n      }\n\n      if (!(chunkRemaining = strtol(inputPtr, NULL, 0x10))) {\n         dc->finished = TRUE;\n         break;   /* A chunk length of 0 means we're done! */\n      }\n      inputRemaining -= (eol - inputPtr) + 1;\n      inputPtr = eol + 1;\n      chunkRemaining += 2; /* CRLF at the end of every chunk */\n   }\n\n   /* If we have a partial chunk header, save it for next time. */\n   dStr_erase(dc->leftover, 0, inputPtr - dc->leftover->str);\n\n   *(int *)dc->state = chunkRemaining;\n   return output;\n}\n\nbool_t a_Decode_transfer_finished(DecodeTransfer *dc)\n{\n   return dc->finished;\n}\n\nvoid a_Decode_transfer_free(DecodeTransfer *dc)\n{\n   dFree(dc->state);\n   dStr_free(dc->leftover, 1);\n   dFree(dc);\n}\n\nstatic void Decode_compression_free(Decode *dc)\n{\n   (void)inflateEnd((z_stream *)dc->state);\n\n   dFree(dc->state);\n   dFree(dc->buffer);\n}\n\n/*\n * BUG: A fair amount of duplicated code exists in the gzip/deflate decoding,\n * but an attempt to pull out the common code left everything too contorted\n * for what it accomplished.\n */\n\n/*\n * Decode gzipped data\n */\nstatic Dstr *Decode_gzip(Decode *dc, const char *instr, int inlen)\n{\n   int rc = Z_OK;\n\n   z_stream *zs = (z_stream *)dc->state;\n\n   int inputConsumed = 0;\n   Dstr *output = dStr_new(\"\");\n\n   while ((rc == Z_OK) && (inputConsumed < inlen)) {\n      zs->next_in = (Bytef *)instr + inputConsumed;\n      zs->avail_in = inlen - inputConsumed;\n\n      zs->next_out = (Bytef *)dc->buffer;\n      zs->avail_out = bufsize;\n\n      rc = inflate(zs, Z_SYNC_FLUSH);\n\n      dStr_append_l(output, dc->buffer, zs->total_out);\n\n      if ((rc == Z_OK) || (rc == Z_STREAM_END)) {\n         // Z_STREAM_END at end of file\n\n         inputConsumed += zs->total_in;\n         zs->total_out = 0;\n         zs->total_in = 0;\n      } else if (rc == Z_DATA_ERROR) {\n         MSG_ERR(\"gzip decompression error\\n\");\n      }\n   }\n   return output;\n}\n\n/*\n * Decode (raw) deflated data\n */\nstatic Dstr *Decode_raw_deflate(Decode *dc, const char *instr, int inlen)\n{\n   int rc = Z_OK;\n\n   z_stream *zs = (z_stream *)dc->state;\n\n   int inputConsumed = 0;\n   Dstr *output = dStr_new(\"\");\n\n   while ((rc == Z_OK) && (inputConsumed < inlen)) {\n      zs->next_in = (Bytef *)instr + inputConsumed;\n      zs->avail_in = inlen - inputConsumed;\n\n      zs->next_out = (Bytef *)dc->buffer;\n      zs->avail_out = bufsize;\n\n      rc = inflate(zs, Z_SYNC_FLUSH);\n\n      dStr_append_l(output, dc->buffer, zs->total_out);\n\n      if ((rc == Z_OK) || (rc == Z_STREAM_END)) {\n         // Z_STREAM_END at end of file\n\n         inputConsumed += zs->total_in;\n         zs->total_out = 0;\n         zs->total_in = 0;\n      } else if (rc == Z_DATA_ERROR) {\n         MSG_ERR(\"raw deflate decompression also failed\\n\");\n      }\n   }\n   return output;\n}\n\n/*\n * Decode deflated data, initially presuming that the required zlib wrapper\n * is there. On data error, switch to Decode_raw_deflate().\n */\nstatic Dstr *Decode_deflate(Decode *dc, const char *instr, int inlen)\n{\n   int rc = Z_OK;\n\n   z_stream *zs = (z_stream *)dc->state;\n\n   int inputConsumed = 0;\n   Dstr *output = dStr_new(\"\");\n\n   while ((rc == Z_OK) && (inputConsumed < inlen)) {\n      zs->next_in = (Bytef *)instr + inputConsumed;\n      zs->avail_in = inlen - inputConsumed;\n\n      zs->next_out = (Bytef *)dc->buffer;\n      zs->avail_out = bufsize;\n\n      rc = inflate(zs, Z_SYNC_FLUSH);\n\n      dStr_append_l(output, dc->buffer, zs->total_out);\n\n      if ((rc == Z_OK) || (rc == Z_STREAM_END)) {\n         // Z_STREAM_END at end of file\n\n         inputConsumed += zs->total_in;\n         zs->total_out = 0;\n         zs->total_in = 0;\n      } else if (rc == Z_DATA_ERROR) {\n         MSG_WARN(\"Deflate decompression error. Certain servers illegally fail\"\n                 \" to send data in a zlib wrapper. Let's try raw deflate.\\n\");\n         dStr_free(output, 1);\n         (void)inflateEnd(zs);\n         dFree(dc->state);\n         dc->state = zs = dNew(z_stream, 1);\n         zs->zalloc = NULL;\n         zs->zfree = NULL;\n         zs->next_in = NULL;\n         zs->avail_in = 0;\n         dc->decode = Decode_raw_deflate;\n\n         // Negative value means that we want raw deflate.\n         inflateInit2(zs, -MAX_WBITS);\n\n         return Decode_raw_deflate(dc, instr, inlen);\n      }\n   }\n   return output;\n}\n\n/*\n * Translate to desired character set (UTF-8)\n */\nstatic Dstr *Decode_charset(Decode *dc, const char *instr, int inlen)\n{\n   inbuf_t *inPtr;\n   char *outPtr;\n   size_t inLeft, outRoom;\n\n   Dstr *output = dStr_new(\"\");\n   int rc = 0;\n\n   dStr_append_l(dc->leftover, instr, inlen);\n   inPtr = dc->leftover->str;\n   inLeft = dc->leftover->len;\n\n   while ((rc != EINVAL) && (inLeft > 0)) {\n\n      outPtr = dc->buffer;\n      outRoom = bufsize;\n\n      rc = iconv((iconv_t)dc->state, &inPtr, &inLeft, &outPtr, &outRoom);\n\n      // iconv() on success, number of bytes converted\n      //         -1, errno == EILSEQ illegal byte sequence found\n      //                      EINVAL partial character ends source buffer\n      //                      E2BIG  destination buffer is full\n\n      dStr_append_l(output, dc->buffer, bufsize - outRoom);\n\n      if (rc == -1)\n         rc = errno;\n      if (rc == EILSEQ){\n         inPtr++;\n         inLeft--;\n         dStr_append_l(output, utf8_replacement_char,\n                       sizeof(utf8_replacement_char) - 1);\n      }\n   }\n   dStr_erase(dc->leftover, 0, dc->leftover->len - inLeft);\n\n   return output;\n}\n\nstatic void Decode_charset_free(Decode *dc)\n{\n   /* iconv_close() frees dc->state */\n   (void)iconv_close((iconv_t)(dc->state));\n\n   dFree(dc->buffer);\n   dStr_free(dc->leftover, 1);\n}\n\n/*\n * Initialize transfer decoder. Currently handles \"chunked\".\n */\nDecodeTransfer *a_Decode_transfer_init(const char *format)\n{\n   DecodeTransfer *dc = NULL;\n\n   if (format && !dStrAsciiCasecmp(format, \"chunked\")) {\n      int *chunk_remaining = dNew(int, 1);\n      *chunk_remaining = 0;\n      dc = dNew(DecodeTransfer, 1);\n      dc->leftover = dStr_new(\"\");\n      dc->state = chunk_remaining;\n      dc->finished = FALSE;\n      _MSG(\"chunked!\\n\");\n   }\n   return dc;\n}\n\nstatic Decode *Decode_content_init_common()\n{\n   z_stream *zs = dNew(z_stream, 1);\n   Decode *dc = dNew(Decode, 1);\n\n   zs->zalloc = NULL;\n   zs->zfree = NULL;\n   zs->next_in = NULL;\n   zs->avail_in = 0;\n   dc->state = zs;\n   dc->buffer = dNew(char, bufsize);\n\n   dc->free = Decode_compression_free;\n   dc->leftover = NULL; /* not used */\n   return dc;\n}\n\n/*\n * Initialize content decoder. Currently handles 'gzip' and 'deflate'.\n */\nDecode *a_Decode_content_init(const char *format)\n{\n   z_stream *zs;\n   Decode *dc = NULL;\n\n   if (format && *format) {\n      if (!dStrAsciiCasecmp(format, \"gzip\") ||\n          !dStrAsciiCasecmp(format, \"x-gzip\")) {\n         _MSG(\"gzipped data!\\n\");\n\n         dc = Decode_content_init_common();\n         zs = (z_stream *)dc->state;\n         /* 16 is a magic number for gzip decoding */\n         inflateInit2(zs, MAX_WBITS+16);\n\n         dc->decode = Decode_gzip;\n      } else if (!dStrAsciiCasecmp(format, \"deflate\")) {\n         _MSG(\"deflated data!\\n\");\n\n         dc = Decode_content_init_common();\n         zs = (z_stream *)dc->state;\n         inflateInit(zs);\n\n         dc->decode = Decode_deflate;\n      } else {\n         MSG(\"Content-Encoding '%s' not recognized.\\n\", format);\n      }\n   }\n   return dc;\n}\n\n/*\n * Initialize decoder to translate from any character set known to iconv()\n * to UTF-8.\n *\n * GNU iconv(1) will provide a list of known character sets if invoked with\n * the \"--list\" flag.\n */\nDecode *a_Decode_charset_init(const char *format)\n{\n   Decode *dc = NULL;\n\n   if (format &&\n       strlen(format) &&\n       dStrAsciiCasecmp(format,\"UTF-8\")) {\n\n      iconv_t ic = iconv_open(\"UTF-8\", format);\n      if (ic != (iconv_t) -1) {\n           dc = dNew(Decode, 1);\n           dc->state = ic;\n           dc->buffer = dNew(char, bufsize);\n           dc->leftover = dStr_new(\"\");\n\n           dc->decode = Decode_charset;\n           dc->free = Decode_charset_free;\n      } else {\n         MSG_WARN(\"Unable to convert from character encoding: '%s'\\n\", format);\n      }\n   }\n   return dc;\n}\n\n/*\n * Decode data.\n */\nDstr *a_Decode_process(Decode *dc, const char *instr, int inlen)\n{\n   return dc->decode(dc, instr, inlen);\n}\n\n/*\n * Free the decoder.\n */\nvoid a_Decode_free(Decode *dc)\n{\n   if (dc) {\n      dc->free(dc);\n      dFree(dc);\n   }\n}\n"
  },
  {
    "path": "src/decode.h",
    "content": "#ifndef __DECODE_H__\n#define __DECODE_H__\n\n#include \"../dlib/dlib.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\ntypedef struct Decode {\n   char *buffer;\n   Dstr *leftover;\n   void *state;\n   Dstr *(*decode) (struct Decode *dc, const char *instr, int inlen);\n   void (*free) (struct Decode *dc);\n} Decode;\n\n/* I'm not going to shoehorn the decoders into the same form anymore. They\n * can evolve independently.\n */\ntypedef struct DecodeTransfer {\n   Dstr *leftover;\n   void *state;\n   bool_t finished;    /* has the terminating chunk been seen? */\n} DecodeTransfer;\n\nDecodeTransfer *a_Decode_transfer_init(const char *format);\nDstr *a_Decode_transfer_process(DecodeTransfer *dc, const char *instr,\n                                int inlen);\nbool_t a_Decode_transfer_finished(DecodeTransfer *dc);\nvoid a_Decode_transfer_free(DecodeTransfer *dc);\n\nDecode *a_Decode_content_init(const char *format);\nDecode *a_Decode_charset_init(const char *format);\nDstr *a_Decode_process(Decode *dc, const char *instr, int inlen);\nvoid a_Decode_free(Decode *dc);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __DECODE_H__ */\n"
  },
  {
    "path": "src/dgif.h",
    "content": "#ifndef __GIF_H__\n#define __GIF_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#include \"url.h\"\n#include \"image.hh\"\n\n\nvoid *a_Gif_new(DilloImage *Image, DilloUrl *url, int version);\nvoid a_Gif_callback(int Op, void *data);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* !__GIF_H__ */\n"
  },
  {
    "path": "src/dialog.cc",
    "content": "/*\n * File: dialog.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n// UI dialogs\n\n#include <math.h> // for rint()\n\n#include <FL/fl_ask.H>\n#include <FL/Fl_Window.H>\n#include <FL/Fl_File_Chooser.H>\n#include <FL/Fl_Return_Button.H>\n#include <FL/Fl_Text_Display.H>\n#include <FL/Fl_Button.H>\n#include <FL/Fl_Return_Button.H>\n#include <FL/Fl_Output.H>\n#include <FL/Fl_Input.H>\n#include <FL/Fl_Secret_Input.H>\n#include <FL/Fl_Choice.H>\n#include <FL/Fl_Menu_Item.H>\n\n#include \"msg.h\"\n#include \"dialog.hh\"\n#include \"misc.h\"\n#include \"prefs.h\"\n\n/*\n * Local Data\n */\nstatic int input_answer;\nstatic char *input_str = NULL;\nstatic int choice_answer;\n\n\n/*\n * Local sub classes\n */\n\n//----------------------------------------------------------------------------\n/*\n * Used to enable CTRL+{a,e,d,k} in search dialog (for start,end,del,cut)\n * TODO: bind down arrow to a search engine selection list.\n */\nclass CustInput3 : public Fl_Input {\npublic:\n   CustInput3 (int x, int y, int w, int h, const char* l=0) :\n      Fl_Input(x,y,w,h,l) {};\n   int handle(int e);\n};\n\nint CustInput3::handle(int e)\n{\n   int k = Fl::event_key();\n\n   _MSG(\"CustInput3::handle event=%d\\n\", e);\n\n   // We're only interested in some flags\n   unsigned modifier = Fl::event_state() & (FL_SHIFT | FL_CTRL | FL_ALT);\n\n   if (e == FL_KEYBOARD && modifier == FL_CTRL) {\n      if (k == 'A') {\n         position(size(), 0);\n         return 1;\n      } else if (k == 'a' || k == 'e') {\n         position(k == 'a' ? 0 : size());\n         return 1;\n      } else if (k == 'k') {\n         cut(position(), size());\n         return 1;\n      } else if (k == 'd') {\n         cut(position(), position()+1);\n         return 1;\n      }\n   }\n   return Fl_Input::handle(e);\n}\n\n/*\n * Used to make the ENTER key activate the CustChoice\n */\nclass CustChoice2 : public Fl_Choice {\npublic:\n   CustChoice2 (int x, int y, int w, int h, const char* l=0) :\n      Fl_Choice(x,y,w,h,l) {};\n   int handle(int e) {\n      if (e == FL_KEYBOARD &&\n          (Fl::event_key() == FL_Enter || Fl::event_key() == FL_Down) &&\n          (Fl::event_state() & (FL_SHIFT|FL_CTRL|FL_ALT|FL_META)) == 0) {\n         return Fl_Choice::handle(FL_PUSH);\n      }\n      return Fl_Choice::handle(e);\n   };\n};\n\nclass EnterButton : public Fl_Button {\npublic:\n   EnterButton (int x,int y,int w,int h, const char* label = 0) :\n      Fl_Button (x,y,w,h,label) {};\n   int handle(int e);\n};\n\nint EnterButton::handle(int e)\n{\n   if (e == FL_KEYBOARD && Fl::focus() == this && Fl::event_key() == FL_Enter){\n      set_changed();\n      simulate_key_action();\n      do_callback();\n      return 1;\n   }\n   return Fl_Button::handle(e);\n}\n\n//----------------------------------------------------------------------------\n\n\n/*\n * Display a message in a popup window.\n */\nvoid a_Dialog_msg(const char *title, const char *msg)\n{\n   if (!(title && *title))\n      title = \"Dillo: Message\";\n   fl_message_title(title);\n   fl_message(\"%s\", msg);\n}\n\n\n/*\n * Callback for a_Dialog_input()\n */\nstatic void input_cb(Fl_Widget *button, void *number)\n{\n  input_answer = VOIDP2INT(number);\n  button->window()->hide();\n}\n\n/*\n * Dialog for one line of Input with a message.\n * avoids the sound bell in fl_input(), and allows customization\n *\n * Return value: string on success, NULL upon Cancel or Close window\n */\nconst char *a_Dialog_input(const char *title, const char *msg)\n{\n   static Fl_Menu_Item *pm = 0;\n   int ww = 450, wh = 130, gap = 10, ih = 60, bw = 80, bh = 30;\n\n   input_answer = 0;\n\n   if (!(title && *title))\n      title = \"Dillo: Input\";\n\n   Fl_Window *window = new Fl_Window(ww,wh,title);\n   window->set_modal();\n   window->begin();\n    Fl_Group* ib = new Fl_Group(0,0,window->w(),window->h());\n    ib->begin();\n    window->resizable(ib);\n\n    /* '?' Icon */\n    Fl_Box* o = new Fl_Box(gap, gap, ih, ih);\n    o->box(FL_THIN_UP_BOX);\n    o->labelfont(FL_TIMES_BOLD);\n    o->labelsize(34);\n    o->label(\"?\");\n    o->show();\n\n    Fl_Box *box = new Fl_Box(ih+2*gap,gap,ww-(ih+3*gap),ih/2, msg);\n    box->labelfont(FL_HELVETICA);\n    box->labelsize(14);\n    box->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE|FL_ALIGN_CLIP|FL_ALIGN_WRAP);\n\n    CustInput3 *c_inp = new CustInput3(ih+2*gap,gap+ih/2+gap,ww-(ih+3*gap),24);\n    c_inp->labelsize(14);\n    c_inp->textsize(14);\n\n    CustChoice2 *ch = new CustChoice2(1*gap,ih+3*gap,180,24);\n    if (!pm) {\n       int n_it = dList_length(prefs.search_urls);\n       pm = new Fl_Menu_Item[n_it+1];\n       memset(pm, '\\0', sizeof(Fl_Menu_Item[n_it+1]));\n       for (int i = 0, j = 0; i < n_it; i++) {\n          char *label, *url, *source;\n          source = (char *)dList_nth_data(prefs.search_urls, i);\n          if (!source || a_Misc_parse_search_url(source, &label, &url) < 0)\n             continue;\n          pm[j++].label(FL_NORMAL_LABEL, strdup(label));\n       }\n    }\n    ch->tooltip(\"Select search engine\");\n    ch->menu(pm);\n    ch->value(prefs.search_url_idx);\n\n    int xpos = ww-2*(gap+bw), ypos = ih+3*gap;\n    Fl_Return_Button *rb = new Fl_Return_Button(xpos, ypos, bw, bh, \"OK\");\n    rb->align(FL_ALIGN_INSIDE|FL_ALIGN_CLIP);\n    rb->box(FL_UP_BOX);\n    rb->callback(input_cb, INT2VOIDP(1));\n\n    xpos = ww-(gap+bw);\n    Fl_Button *b = new Fl_Button(xpos, ypos, bw, bh, \"Cancel\");\n    b->align(FL_ALIGN_INSIDE|FL_ALIGN_CLIP);\n    b->box(FL_UP_BOX);\n    b->callback(input_cb, INT2VOIDP(2));\n\n   window->end();\n\n   window->show();\n   while (window->shown())\n      Fl::wait();\n   if (input_answer == 1) {\n      /* we have a string, save it */\n      dFree(input_str);\n      input_str = dStrdup(c_inp->value());\n      prefs.search_url_idx = ch->value();\n   }\n   delete window;\n\n   return (input_answer == 1) ? input_str : NULL;\n}\n\n/*\n * Dialog for password\n */\nconst char *a_Dialog_passwd(const char *title, const char *msg)\n{\n   if (!(title && *title))\n      title = \"Dillo: Password\";\n   fl_message_title(title);\n   return fl_password(\"%s\", \"\", msg);\n}\n\n/*\n * Show the save file dialog.\n *\n * Return: pointer to chosen filename, or NULL on Cancel.\n */\nconst char *a_Dialog_save_file(const char *title,\n                               const char *pattern, const char *fname)\n{\n   return fl_file_chooser(title, pattern, fname);\n}\n\n/*\n * Show the select file dialog.\n *\n * Return: pointer to chosen filename, or NULL on Cancel.\n */\nconst char *a_Dialog_select_file(const char *title,\n                                 const char *pattern, const char *fname)\n{\n   /*\n    * FileChooser::type(MULTI) appears to allow multiple files to be selected,\n    * but just follow save_file's path for now.\n    */\n   return a_Dialog_save_file(title, pattern, fname);\n}\n\n/*\n * Show the open file dialog.\n *\n * Return: pointer to chosen filename, or NULL on Cancel.\n */\nchar *a_Dialog_open_file(const char *title,\n                         const char *pattern, const char *fname)\n{\n   const char *fc_name;\n\n   fc_name = fl_file_chooser(title, pattern, fname);\n   return (fc_name) ? a_Misc_escape_chars(fc_name, \"% #\") : NULL;\n}\n\n/*\n * Close text window.\n */\nstatic void text_window_close_cb(Fl_Widget *, void *vtd)\n{\n   Fl_Text_Display *td = (Fl_Text_Display *)vtd;\n   Fl_Text_Buffer *buf = td->buffer();\n\n   delete (Fl_Window*)td->window();\n   delete buf;\n}\n\n/*\n * Show a new window with the provided text\n */\nvoid a_Dialog_text_window(const char *title, const char *txt)\n{\n   int wh = prefs.height, ww = prefs.width, bh = 30;\n\n   if (!(title && *title))\n      title = \"Dillo: Text\";\n\n   Fl_Window *window = new Fl_Window(ww, wh, title);\n   Fl_Group::current(0);\n\n\n    Fl_Text_Buffer *buf = new Fl_Text_Buffer();\n    buf->text(txt);\n    Fl_Text_Display *td = new Fl_Text_Display(0,0,ww, wh-bh);\n    td->buffer(buf);\n    td->textsize((int) rint(14.0 * prefs.font_factor));\n\n    /* enable wrapping lines; text uses entire width of window */\n    td->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0);\n   window->add(td);\n\n    Fl_Return_Button *b = new Fl_Return_Button (0, wh-bh, ww, bh, \"Close\");\n    b->callback(text_window_close_cb, td);\n   window->add(b);\n\n   window->callback(text_window_close_cb, td);\n   window->resizable(td);\n   window->show();\n}\n\n/*--------------------------------------------------------------------------*/\n\nstatic void choice_cb(Fl_Widget *button, void *number)\n{\n  choice_answer = VOIDP2INT(number);\n  _MSG(\"choice_cb: %d\\n\", choice_answer);\n\n  button->window()->hide();\n}\n\n/*\n * Make a question-dialog with a question and alternatives.\n * Last parameter must be NULL.\n *\n * Return value: 0 = dialog was cancelled, >0 = selected alternative.\n */\nint a_Dialog_choice(const char *title, const char *msg, ...)\n{\n   va_list ap;\n   int i, n;\n\n   if (title == NULL || *title == '\\0')\n      title = \"Dillo: Choice\";\n\n   va_start(ap, msg);\n   for (n = 0; va_arg(ap, char *) != NULL; n++);\n   va_end(ap);\n\n   if (n == 0) {\n      MSG_ERR(\"Dialog_choice: no alternatives.\\n\");\n      return 0;\n   }\n\n   int gap = 8;\n   int ww = 140 + n * 60, wh = 120;\n   int bw = (ww - gap) / n - gap, bh = 45;\n\n   Fl_Window *window = new Fl_Window(ww, wh, title);\n   window->set_modal();\n   window->begin();\n\n    Fl_Text_Buffer *buf = new Fl_Text_Buffer();\n    buf->text(msg);\n    Fl_Text_Display *td = new Fl_Text_Display(0, 0, ww, wh - bh);\n    td->buffer(buf);\n    td->textsize((int) rint(14.0 * prefs.font_factor));\n    td->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0);\n    \n    window->resizable(td);\n\n    int xpos = gap;\n    va_start(ap, msg);\n    for (i = 1; i <= n; i++) {\n       Fl_Button *b = new EnterButton(xpos, wh-bh, bw, bh, va_arg(ap, char *));\n       b->align(FL_ALIGN_WRAP | FL_ALIGN_CLIP);\n       b->box(FL_UP_BOX);\n       b->callback(choice_cb, INT2VOIDP(i));\n       xpos += bw + gap;\n       /* TODO: set focus to the *-prefixed alternative */\n    }\n    va_end(ap);\n   window->end();\n\n   choice_answer = 0;\n\n   window->show();\n   while (window->shown())\n      Fl::wait();\n   _MSG(\"Dialog_choice answer = %d\\n\", answer);\n   td->buffer(NULL);\n   delete buf;\n   delete window;\n\n   return choice_answer;\n}\n\n/*--------------------------------------------------------------------------*/\nstatic void Dialog_user_password_cb(Fl_Widget *button, void *)\n{\n   button->window()->user_data(button);\n   button->window()->hide();\n}\n\n/*\n * Make a user/password dialog.\n * Call the callback with the result (OK or not) and the given user and\n *   password if OK.\n */\nint a_Dialog_user_password(const char *title, const char *msg,\n                           UserPasswordCB cb, void *vp)\n{\n   int ok = 0, window_h = 280, y, msg_w, msg_h;\n   const int window_w = 300, input_x = 80, input_w = 200, input_h = 30,\n      button_h = 30;\n\n   /* window is resized below */\n   if (!(title && *title))\n      title = \"Dillo: User/Password\";\n   Fl_Window *window = new Fl_Window(window_w,window_h,title);\n   Fl_Group::current(0);\n   window->user_data(NULL);\n\n   /* message */\n   y = 20;\n   msg_w = window_w - 40;\n   Fl_Box *msg_box = new Fl_Box(20, y, msg_w, 100); /* resized below */\n   msg_box->label(msg);\n   msg_box->labelfont(FL_HELVETICA);\n   msg_box->labelsize(14);\n   msg_box->align(FL_ALIGN_INSIDE | FL_ALIGN_TOP_LEFT | FL_ALIGN_WRAP);\n\n   fl_font(msg_box->labelfont(), msg_box->labelsize());\n   msg_w -= 6; /* The label doesn't fill the entire box. */\n   fl_measure(msg_box->label(), msg_w, msg_h, 0); // fl_measure wraps at msg_w\n   msg_box->size(msg_box->w(), msg_h);\n   window->add(msg_box);\n\n   /* inputs */\n   y += msg_h + 20;\n   Fl_Input *user_input = new Fl_Input(input_x, y, input_w, input_h, \"User\");\n   user_input->labelsize(14);\n   user_input->textsize(14);\n   window->add(user_input);\n   y += input_h + 10;\n   Fl_Secret_Input *password_input =\n      new Fl_Secret_Input(input_x, y, input_w, input_h, \"Password\");\n   password_input->labelsize(14);\n   password_input->textsize(14);\n   window->add(password_input);\n\n   /* \"OK\" button */\n   y += input_h + 20;\n   Fl_Button *ok_button = new EnterButton(200, y, 50, button_h, \"OK\");\n   ok_button->labelsize(14);\n   ok_button->callback(Dialog_user_password_cb);\n   window->add(ok_button);\n\n   /* \"Cancel\" button */\n   Fl_Button *cancel_button =\n      new EnterButton(50, y, 100, button_h, \"Cancel\");\n   cancel_button->labelsize(14);\n   cancel_button->callback(Dialog_user_password_cb);\n   window->add(cancel_button);\n\n   y += button_h + 20;\n   window_h = y;\n   window->size(window_w, window_h);\n   window->size_range(window_w, window_h, window_w, window_h);\n   window->resizable(window);\n\n   window->show();\n   while (window->shown())\n      Fl::wait();\n\n   ok = ((Fl_Widget *)window->user_data()) == ok_button ? 1 : 0;\n\n   if (ok) {\n      /* call the callback */\n      const char *user, *password;\n      user = user_input->value();\n      password = password_input->value();\n      _MSG(\"a_Dialog_user_passwd: ok = %d\\n\", ok);\n      (*cb)(user, password, vp);\n   }\n   delete window;\n\n   return ok;\n}\n\n"
  },
  {
    "path": "src/dialog.hh",
    "content": "#ifndef __DIALOG_HH__\n#define __DIALOG_HH__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\ntypedef void (*UserPasswordCB)(const char *user, const char *password,\n                               void *vp);\n\nvoid a_Dialog_msg(const char *title, const char *msg);\nint a_Dialog_choice(const char *title, const char *msg, ...);\nint a_Dialog_user_password(const char *title, const char *msg,\n                           UserPasswordCB cb, void *vp);\nconst char *a_Dialog_input(const char *title, const char *msg);\nconst char *a_Dialog_passwd(const char *title, const char *msg);\nconst char *a_Dialog_save_file(const char *title,\n                               const char *pattern, const char *fname);\nconst char *a_Dialog_select_file(const char *title,\n                                 const char *pattern, const char *fname);\nchar *a_Dialog_open_file(const char *title,\n                         const char *pattern, const char *fname);\nvoid a_Dialog_text_window(const char *title, const char *txt);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif // __DIALOG_HH__\n"
  },
  {
    "path": "src/dicache.c",
    "content": "/*\n * File: dicache.c\n *\n * Copyright 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <string.h>         /* for memset */\n#include <stdlib.h>\n\n#include \"msg.h\"\n#include \"image.hh\"\n#include \"imgbuf.hh\"\n#include \"web.hh\"\n#include \"dicache.h\"\n#include \"dpng.h\"\n#include \"dgif.h\"\n#include \"djpeg.h\"\n\n\nenum {\n   DIC_Gif,\n   DIC_Png,\n   DIC_Jpeg\n};\n\n\n/*\n * List of DICacheEntry. May hold several versions of the same image,\n * although most of the time it holds just one.\n */\nstatic Dlist *CachedIMGs = NULL;\n\nstatic uint_t dicache_size_total; /* invariant: dicache_size_total is\n                                   * the sum of the image sizes (3*w*h)\n                                   * of all the images in the dicache. */\n\n/*\n * Compare function for image entries\n */\nstatic int Dicache_entry_cmp(const void *v1, const void *v2)\n{\n   const DICacheEntry *e1 = v1, *e2 = v2;\n\n   int st = a_Url_cmp(e1->url, e2->url);\n   if (st == 0) {\n      if (e2->version == DIC_Last)\n         st = (e1->Flags & DIF_Last ? 0 : -1);\n      else\n         st = (e1->version - e2->version);\n   }\n   return st;\n}\n\n/*\n * Initialize dicache data\n */\nvoid a_Dicache_init(void)\n{\n   CachedIMGs = dList_new(256);\n   dicache_size_total = 0;\n}\n\n/*\n * Create, and initialize a new, empty, dicache entry\n */\nstatic DICacheEntry *Dicache_entry_new(void)\n{\n   DICacheEntry *entry = dNew(DICacheEntry, 1);\n\n   entry->width = 0;\n   entry->height = 0;\n   entry->Flags = DIF_Valid;\n   entry->SurvCleanup = 0;\n   entry->type = DILLO_IMG_TYPE_NOTSET;\n   entry->cmap = NULL;\n   entry->v_imgbuf = NULL;\n   entry->RefCount = 1;\n   entry->TotalSize = 0;\n   entry->ScanNumber = 0;\n   entry->BitVec = NULL;\n   entry->State = DIC_Empty;\n   entry->version = 1;\n\n   entry->Decoder = NULL;\n   entry->DecoderData = NULL;\n   entry->DecodedSize = 0;\n\n   return entry;\n}\n\n/*\n * Add a new entry in the dicache\n * (a single URL may have several entries)\n */\nstatic DICacheEntry *Dicache_add_entry(const DilloUrl *Url)\n{\n   DICacheEntry e, *entry, *last;\n\n   entry = Dicache_entry_new();\n   e.url = (DilloUrl*)Url;\n   e.version = DIC_Last;\n   last = dList_find_sorted(CachedIMGs, &e, Dicache_entry_cmp);\n   if (last) {\n      /* URL is already in CachedIMGs, make a new version */\n      last->Flags &= ~DIF_Last;\n      entry->version = last->version + 1;\n   }\n   entry->url = a_Url_dup(Url);\n   entry->Flags |= DIF_Last;\n   dList_insert_sorted(CachedIMGs, entry, Dicache_entry_cmp);\n\n   return entry;\n}\n\n/*\n * Search a particular version of a URL in the Dicache.\n * Return value: a pointer to the entry if found; NULL otherwise.\n *\n * Notes: DIC_Last means last version of the image.\n *        version zero is not allowed.\n */\nDICacheEntry *a_Dicache_get_entry(const DilloUrl *Url, int version)\n{\n   DICacheEntry e;\n   DICacheEntry *entry = NULL;\n\n   dReturn_val_if_fail(version != 0, NULL);\n   e.url = (DilloUrl*)Url;\n   e.version = version;\n   entry = dList_find_sorted(CachedIMGs, &e, Dicache_entry_cmp);\n   if (entry && !(entry->Flags & DIF_Valid) && version == DIC_Last)\n      entry = NULL;\n   return entry;\n}\n\n/*\n * Actually free a dicache entry, given the URL and the version number.\n */\nstatic void Dicache_remove(const DilloUrl *Url, int version)\n{\n   DICacheEntry e, *entry;\n\n   _MSG(\"Dicache_remove url=%s\\n\", URL_STR(Url));\n   e.url = (DilloUrl*)Url;\n   e.version = version;\n   entry = dList_find_sorted(CachedIMGs, &e, Dicache_entry_cmp);\n   dReturn_if (entry == NULL);\n\n   _MSG(\"Dicache_remove Imgbuf=%p Decoder=%p DecoderData=%p\\n\",\n       entry->v_imgbuf, entry->Decoder, entry->DecoderData);\n   /* Eliminate this dicache entry */\n   dList_remove(CachedIMGs, entry);\n   dicache_size_total -= entry->TotalSize;\n\n   /* entry cleanup */\n   a_Url_free(entry->url);\n   dFree(entry->cmap);\n   a_Bitvec_free(entry->BitVec);\n   a_Imgbuf_unref(entry->v_imgbuf);\n   if (entry->Decoder) {\n      entry->Decoder(CA_Abort, entry->DecoderData);\n   }\n   dFree(entry);\n}\n\n/*\n * Unrefs the counter of a dicache entry (it counts cache clients).\n * If there're no clients and no imgbuf, remove the entry.\n * Otherwise, let a_Dicache_cleanup() do the job later\n * (keeping it cached meanwhile for e.g. reload, repush, back/fwd).\n */\nvoid a_Dicache_unref(const DilloUrl *Url, int version)\n{\n   DICacheEntry *entry;\n\n   if ((entry = a_Dicache_get_entry(Url, version))) {\n      _MSG(\"a_Dicache_unref: RefCount=%d State=%d ImgbufLastRef=%d\\n\",\n          entry->RefCount, entry->State,\n          entry->v_imgbuf ? a_Imgbuf_last_reference(entry->v_imgbuf) : -1);\n      if (entry->RefCount > 0) --entry->RefCount;\n      if (entry->RefCount == 0 && entry->v_imgbuf == NULL)\n         Dicache_remove(Url, version);\n   }\n}\n\n/*\n * Refs the counter of a dicache entry.\n */\nDICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version)\n{\n   DICacheEntry *entry;\n\n   if ((entry = a_Dicache_get_entry(Url, version))) {\n      ++entry->RefCount;\n   }\n   return entry;\n}\n\n/*\n * Invalidate this entry. This is used for the reloading mechanism.\n * Can't erase current versions, but a_Dicache_get_entry(url, DIC_Last)\n * must return NULL.\n */\nvoid a_Dicache_invalidate_entry(const DilloUrl *Url)\n{\n   DICacheEntry *entry = a_Dicache_get_entry(Url, DIC_Last);\n   if (entry)\n      entry->Flags &= ~DIF_Valid;\n}\n\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Set image's width, height & type\n * - 'width' and 'height' come from the image data.\n * - HTML width and height attrs are handled with setNonCssHint.\n * - CSS sizing is handled by the CSS engine.\n */\nvoid a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image,\n                         uint_t width, uint_t height, DilloImgType type,\n                         double gamma)\n{\n   DICacheEntry *DicEntry;\n\n   _MSG(\"a_Dicache_set_parms (%s)\\n\", URL_STR(url));\n   dReturn_if_fail ( Image != NULL && width && height );\n   /* Find the DicEntry for this Image */\n   DicEntry = a_Dicache_get_entry(url, version);\n   dReturn_if_fail ( DicEntry != NULL );\n   /* Parameters already set? Don't do it twice. */\n   dReturn_if_fail ( DicEntry->State < DIC_SetParms );\n\n   _MSG(\"  RefCount=%d version=%d\\n\", DicEntry->RefCount, DicEntry->version);\n\n   /* BUG: there's just one image-type now */\n   #define I_RGB 0\n   DicEntry->v_imgbuf =\n      a_Imgbuf_new(Image->layout, I_RGB, width, height, gamma);\n\n   DicEntry->TotalSize = width * height * 3;\n   DicEntry->width = width;\n   DicEntry->height = height;\n   DicEntry->type = type;\n   DicEntry->BitVec = a_Bitvec_new((int)height);\n   DicEntry->State = DIC_SetParms;\n\n   dicache_size_total += DicEntry->TotalSize;\n}\n\n/*\n * Implement the set_cmap method for the Image\n */\nvoid a_Dicache_set_cmap(DilloUrl *url, int version, int bg_color,\n                        const uchar_t *cmap, uint_t num_colors,\n                        int num_colors_max, int bg_index)\n{\n   DICacheEntry *DicEntry = a_Dicache_get_entry(url, version);\n\n   _MSG(\"a_Dicache_set_cmap\\n\");\n   dReturn_if_fail ( DicEntry != NULL );\n\n   dFree(DicEntry->cmap);\n   DicEntry->cmap = dNew0(uchar_t, 3 * num_colors_max);\n   memcpy(DicEntry->cmap, cmap, 3 * num_colors);\n   if (bg_index >= 0 && (uint_t)bg_index < num_colors) {\n      DicEntry->cmap[bg_index * 3]     = (bg_color >> 16) & 0xff;\n      DicEntry->cmap[bg_index * 3 + 1] = (bg_color >> 8) & 0xff;\n      DicEntry->cmap[bg_index * 3 + 2] = (bg_color) & 0xff;\n   }\n\n   DicEntry->State = DIC_SetCmap;\n}\n\n/*\n * Reset for a new scan from a multiple-scan image.\n */\nvoid a_Dicache_new_scan(const DilloUrl *url, int version)\n{\n   DICacheEntry *DicEntry;\n\n   _MSG(\"a_Dicache_new_scan\\n\");\n   dReturn_if_fail ( url != NULL );\n   DicEntry = a_Dicache_get_entry(url, version);\n   dReturn_if_fail ( DicEntry != NULL );\n   if (DicEntry->State < DIC_SetParms) {\n      MSG(\"a_Dicache_new_scan before DIC_SetParms\\n\");\n      exit(1);\n   }\n   a_Bitvec_clear(DicEntry->BitVec);\n   DicEntry->ScanNumber++;\n   a_Imgbuf_new_scan(DicEntry->v_imgbuf);\n}\n\n/*\n * Implement the write method\n * (Write a scan line into the Dicache entry)\n * buf: row buffer\n * Y  : row number\n */\nvoid a_Dicache_write(DilloUrl *url, int version, const uchar_t *buf, uint_t Y)\n{\n   DICacheEntry *DicEntry;\n\n   _MSG(\"a_Dicache_write\\n\");\n   DicEntry = a_Dicache_get_entry(url, version);\n   dReturn_if_fail ( DicEntry != NULL );\n   dReturn_if_fail ( DicEntry->width > 0 && DicEntry->height > 0 );\n\n   /* update the common buffer in the imgbuf */\n   a_Imgbuf_update(DicEntry->v_imgbuf, buf, DicEntry->type,\n                   DicEntry->cmap, DicEntry->width, DicEntry->height, Y);\n\n   a_Bitvec_set_bit(DicEntry->BitVec, (int)Y);\n   DicEntry->State = DIC_Write;\n}\n\n/*\n * Implement the close method of the decoding process\n */\nvoid a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client)\n{\n   DilloWeb *Web = Client->Web;\n   DICacheEntry *DicEntry = a_Dicache_get_entry(url, version);\n\n   dReturn_if_fail ( DicEntry != NULL );\n\n   /* a_Dicache_unref() may free DicEntry */\n   _MSG(\"a_Dicache_close RefCount=%d\\n\", DicEntry->RefCount - 1);\n   _MSG(\"a_Dicache_close DIC_Close=%d State=%d\\n\", DIC_Close, DicEntry->State);\n   _MSG(\" a_Dicache_close imgbuf=%p Decoder=%p DecoderData=%p\\n\",\n        DicEntry->v_imgbuf, DicEntry->Decoder, DicEntry->DecoderData);\n\n   if (DicEntry->State < DIC_Close) {\n      DicEntry->State = DIC_Close;\n      dFree(DicEntry->cmap);\n      DicEntry->cmap = NULL;\n      DicEntry->Decoder = NULL;\n      DicEntry->DecoderData = NULL;\n   }\n   a_Dicache_unref(url, version);\n\n   a_Bw_close_client(Web->bw, Client->Key);\n}\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Generic MIME handler for GIF, JPEG and PNG.\n * Sets a_Dicache_callback as the cache-client,\n * and also sets the image decoder.\n *\n * Parameters:\n *   Type: MIME type\n *   Ptr:  points to a Web structure\n *   Call: Dillo calls this with more data/eod\n *   Data: Decoding data structure\n */\nstatic void *Dicache_image(int ImgType, const char *MimeType, void *Ptr,\n                           CA_Callback_t *Call, void **Data)\n{\n   DilloWeb *web = Ptr;\n   DICacheEntry *DicEntry;\n\n   dReturn_val_if_fail(MimeType && Ptr, NULL);\n\n   if (!web->Image) {\n      web->Image =\n         a_Image_new_with_dw(web->bw->render_layout, NULL, web->bgColor);\n      a_Image_ref(web->Image);\n   }\n\n   DicEntry = a_Dicache_get_entry(web->url, DIC_Last);\n   if (!DicEntry) {\n      /* Create an entry for this image... */\n      DicEntry = Dicache_add_entry(web->url);\n      /* Attach a decoder */\n      if (ImgType == DIC_Jpeg) {\n         DicEntry->Decoder = (CA_Callback_t)a_Jpeg_callback;\n         DicEntry->DecoderData =\n            a_Jpeg_new(web->Image, DicEntry->url, DicEntry->version);\n      } else if (ImgType == DIC_Gif) {\n         DicEntry->Decoder = (CA_Callback_t)a_Gif_callback;\n         DicEntry->DecoderData =\n            a_Gif_new(web->Image, DicEntry->url, DicEntry->version);\n      } else if (ImgType == DIC_Png) {\n         DicEntry->Decoder = (CA_Callback_t)a_Png_callback;\n         DicEntry->DecoderData =\n            a_Png_new(web->Image, DicEntry->url, DicEntry->version);\n      }\n   } else {\n      /* Repeated image */\n      a_Dicache_ref(DicEntry->url, DicEntry->version);\n   }\n   /* Survive three cleanup passes (set to zero = old behaviour). */\n   DicEntry->SurvCleanup = 3;\n\n   *Data = DicEntry->DecoderData;\n   *Call = (CA_Callback_t) a_Dicache_callback;\n\n   return (a_Image_get_dw (web->Image));\n}\n\n/*\n * PNG wrapper for Dicache_image()\n */\nvoid *a_Dicache_png_image(const char *Type, void *Ptr, CA_Callback_t *Call,\n                          void **Data)\n{\n   return Dicache_image(DIC_Png, Type, Ptr, Call, Data);\n}\n\n/*\n * GIF wrapper for Dicache_image()\n */\nvoid *a_Dicache_gif_image(const char *Type, void *Ptr, CA_Callback_t *Call,\n                          void **Data)\n{\n   return Dicache_image(DIC_Gif, Type, Ptr, Call, Data);\n}\n\n/*\n * JPEG wrapper for Dicache_image()\n */\nvoid *a_Dicache_jpeg_image(const char *Type, void *Ptr, CA_Callback_t *Call,\n                           void **Data)\n{\n   return Dicache_image(DIC_Jpeg, Type, Ptr, Call, Data);\n}\n\n/*\n * This function is a cache client; (but feeds its clients from dicache)\n */\nvoid a_Dicache_callback(int Op, CacheClient_t *Client)\n{\n   uint_t i;\n   DilloWeb *Web = Client->Web;\n   DilloImage *Image = Web->Image;\n   DICacheEntry *DicEntry = a_Dicache_get_entry(Web->url, DIC_Last);\n\n   dReturn_if_fail ( DicEntry != NULL );\n\n   /* Copy the version number in the Client */\n   if (Client->Version == 0)\n      Client->Version = DicEntry->version;\n\n   /* Only call the decoder when necessary */\n   if (Op == CA_Send && DicEntry->State < DIC_Close &&\n       DicEntry->DecodedSize < Client->BufSize) {\n      DicEntry->Decoder(Op, Client);\n      DicEntry->DecodedSize = Client->BufSize;\n   } else if (Op == CA_Close || Op == CA_Abort) {\n      if (DicEntry->State < DIC_Close) {\n         DicEntry->Decoder(Op, Client);\n      } else {\n         a_Dicache_close(DicEntry->url, DicEntry->version, Client);\n      }\n   }\n\n   /* when the data stream is not an image 'v_imgbuf' remains NULL */\n   if (Op == CA_Send && DicEntry->v_imgbuf) {\n      if (Image->height == 0 && DicEntry->State >= DIC_SetParms) {\n         /* Set parms */\n         a_Image_set_parms(\n            Image, DicEntry->v_imgbuf, DicEntry->url,\n            DicEntry->version, DicEntry->width, DicEntry->height,\n            DicEntry->type);\n      }\n      if (DicEntry->State == DIC_Write) {\n         if (DicEntry->ScanNumber == Image->ScanNumber) {\n            for (i = 0; i < DicEntry->height; ++i)\n               if (a_Bitvec_get_bit(DicEntry->BitVec, (int)i) &&\n                   !a_Bitvec_get_bit(Image->BitVec, (int)i) )\n                  a_Image_write(Image, i);\n         } else {\n            for (i = 0; i < DicEntry->height; ++i) {\n               if (a_Bitvec_get_bit(DicEntry->BitVec, (int)i) ||\n                   !a_Bitvec_get_bit(Image->BitVec, (int)i)   ||\n                   DicEntry->ScanNumber > Image->ScanNumber + 1) {\n                  a_Image_write(Image, i);\n               }\n               if (!a_Bitvec_get_bit(DicEntry->BitVec, (int)i))\n                  a_Bitvec_clear_bit(Image->BitVec, (int)i);\n            }\n            Image->ScanNumber = DicEntry->ScanNumber;\n         }\n      }\n   } else if (Op == CA_Close) {\n      a_Image_close(Image);\n      a_Bw_close_client(Web->bw, Client->Key);\n   } else if (Op == CA_Abort) {\n      a_Image_abort(Image);\n      a_Bw_close_client(Web->bw, Client->Key);\n   }\n}\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Free the imgbuf (RGB data) of unused entries.\n */\nvoid a_Dicache_cleanup(void)\n{\n   int i;\n   DICacheEntry *entry;\n\n   for (i = 0; (entry = dList_nth_data(CachedIMGs, i)); ++i) {\n      _MSG(\" SurvCleanup = %d\\n\", entry->SurvCleanup);\n      if (entry->RefCount == 0 &&\n          (!entry->v_imgbuf || a_Imgbuf_last_reference(entry->v_imgbuf))) {\n         if (--entry->SurvCleanup >= 0)\n            continue;  /* keep the entry one more pass */\n\n         /* free this unused entry */\n         Dicache_remove(entry->url, entry->version);\n         --i; /* adjust counter */\n      }\n   }\n   _MSG(\"a_Dicache_cleanup: length = %d\\n\", dList_length(CachedIMGs));\n}\n\n/* ------------------------------------------------------------------------- */\n\n/*\n * Deallocate memory used by dicache module\n * (Call this one at exit time, with no cache clients queued)\n */\nvoid a_Dicache_freeall(void)\n{\n   DICacheEntry *entry;\n\n   /* Remove all the dicache entries */\n   while ((entry = dList_nth_data(CachedIMGs, dList_length(CachedIMGs)-1))) {\n      dList_remove_fast(CachedIMGs, entry);\n      a_Url_free(entry->url);\n      dFree(entry->cmap);\n      a_Bitvec_free(entry->BitVec);\n      a_Imgbuf_unref(entry->v_imgbuf);\n      dicache_size_total -= entry->TotalSize;\n      dFree(entry);\n   }\n   dList_free(CachedIMGs);\n}\n"
  },
  {
    "path": "src/dicache.h",
    "content": "#ifndef __DICACHE_H__\n#define __DICACHE_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\n#include \"bitvec.h\"\n#include \"image.hh\"\n#include \"cache.h\"\n\n/* Symbolic name to request the last version of an image */\n#define DIC_Last  -1\n/* Flags: Last version, Valid entry */\n#define DIF_Last  1\n#define DIF_Valid 2\n\n\n/* These will reflect the entry's \"state\" */\ntypedef enum {\n   DIC_Empty,      /* Just created the entry */\n   DIC_SetParms,   /* Parameters set */\n   DIC_SetCmap,    /* Color map set */\n   DIC_Write,      /* Feeding the entry */\n   DIC_Close,      /* Whole image got! */\n   DIC_Abort       /* Image transfer aborted */\n} DicEntryState;\n\ntypedef struct DICacheEntry {\n   DilloUrl *url;          /* Image URL for this entry */\n   DilloImgType type;      /* Image type */\n   uint_t width, height;   /* As taken from image data */\n   short Flags;            /* See Flags */\n   short SurvCleanup;      /* Cleanup-pass survival for unused images */\n   uchar_t *cmap;          /* Color map */\n   void *v_imgbuf;         /* Void pointer to an Imgbuf object */\n   uint_t TotalSize;       /* Amount of memory the image takes up */\n   uint_t ScanNumber;      /* Current decoding scan */\n   bitvec_t *BitVec;       /* Bit vector for decoded rows */\n   DicEntryState State;    /* Current status for this entry */\n   int RefCount;           /* Reference Counter */\n   int version;            /* Version number, used for different\n                              versions of the same URL image */\n\n   uint_t DecodedSize;     /* Size of already decoded data */\n   CA_Callback_t Decoder;  /* Client function */\n   void *DecoderData;      /* Client function data */\n} DICacheEntry;\n\n\nvoid a_Dicache_init (void);\n\nDICacheEntry *a_Dicache_get_entry(const DilloUrl *Url, int version);\n\nvoid *a_Dicache_png_image(const char *Type, void *Ptr, CA_Callback_t *Call,\n                          void **Data);\nvoid *a_Dicache_gif_image(const char *Type, void *Ptr, CA_Callback_t *Call,\n                          void **Data);\nvoid *a_Dicache_jpeg_image(const char *Type, void *Ptr, CA_Callback_t *Call,\n                           void **Data);\nvoid a_Dicache_callback(int Op, CacheClient_t *Client);\n\nvoid a_Dicache_set_parms(DilloUrl *url, int version, DilloImage *Image,\n                         uint_t width, uint_t height, DilloImgType type,\n                         double gamma);\nvoid a_Dicache_set_cmap(DilloUrl *url, int version, int bg_color,\n                        const uchar_t *cmap, uint_t num_colors,\n                        int num_colors_max, int bg_index);\nvoid a_Dicache_new_scan(const DilloUrl *url, int version);\nvoid a_Dicache_write(DilloUrl *url, int version, const uchar_t *buf, uint_t Y);\nvoid a_Dicache_close(DilloUrl *url, int version, CacheClient_t *Client);\n\nvoid a_Dicache_invalidate_entry(const DilloUrl *Url);\nDICacheEntry* a_Dicache_ref(const DilloUrl *Url, int version);\nvoid a_Dicache_unref(const DilloUrl *Url, int version);\nvoid a_Dicache_cleanup(void);\nvoid a_Dicache_freeall(void);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* __DICACHE_H__ */\n"
  },
  {
    "path": "src/digest.c",
    "content": "/*\n * File: digest.c\n *\n * Copyright 2009 Justus Winter  <4winter@informatik.uni-hamburg.de>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <stdlib.h>\n#include \"digest.h\"\n#include \"md5.h\"\n#include \"msg.h\"\n#include \"../dlib/dlib.h\"\n\nstatic const char *ALGORITHM2STR[] = { NULL, \"MD5\", \"MD5-sess\" };\nstatic const char *QOP2STR[] = { NULL, \"auth\", \"auth-int\" };\nstatic const char hexchars[] = \"0123456789abcdef\";\n\nstatic Dstr *md5hexdigest(const Dstr *data)\n{\n   md5_state_t state;\n   md5_byte_t digest[16];\n   Dstr *result = dStr_sized_new(33);\n   int i;\n\n   md5_init(&state);\n   md5_append(&state, (const md5_byte_t *)data->str, data->len);\n   md5_finish(&state, digest);\n\n   for (i = 0; i < 16; i++)\n      dStr_sprintfa(result, \"%02x\", digest[i]);\n   return result;\n}\n\n/*\n * Returns a pointer to a newly allocated string containing a cnonce\n */\nchar *a_Digest_create_cnonce(void)\n{\n   int i;\n   char *result = dNew(char, 33);\n   for (i = 0; i < 32; i++)\n      result[i] = hexchars[rand() % 16];\n   result[32] = 0;\n   return result;\n}\n\n/*\n * This portion only has to be calculated once.\n */\nint a_Digest_compute_digest(AuthRealm_t *realm, const char *username,\n                            const char *passwd)\n{\n   Dstr *a1;\n   Dstr *digest;\n\n   if (realm->algorithm == MD5 || realm->algorithm == ALGORITHMNOTSET) {\n      /* A1 = unq(username-value) \":\" unq(realm-value) \":\" passwd */\n      a1 = dStr_new(NULL);\n      dStr_sprintf(a1, \"%s:%s:%s\", username, realm->name, passwd);\n   } else if (realm->algorithm == MD5SESS) {\n      /* A1 = H( unq(username-value) \":\" unq(realm-value)\n      **         \":\" passwd )\n      **         \":\" unq(nonce-value) \":\" unq(cnonce-value)\n      */\n      Dstr *a0 = dStr_new(NULL);\n      dStr_sprintf(a0, \"%s:%s:%s\", username, realm->name, passwd);\n      Dstr *ha0 = md5hexdigest(a0);\n      a1 = dStr_new(NULL);\n      dStr_sprintf(a1, \"%s:%s:%s\", ha0, realm->nonce, realm->cnonce);\n      dStr_free(a0, 1);\n      dStr_free(ha0, 1);\n   } else {\n      MSG(\"a_Digest_create_auth: Unknown algorithm.\\n\");\n      return 0;\n   }\n\n   digest = md5hexdigest(a1);\n   realm->authorization = digest->str;\n   dStr_shred(a1);\n   dStr_free(a1, 1);\n   dStr_free(digest, 0);\n   return 1;\n}\n\n/*\n * This portion is calculatd for each request.\n */\nstatic Dstr *Digest_create_response(AuthRealm_t *realm, const char *method,\n                                    const char *digest_uri,\n                                    const Dstr *entity_body)\n{\n   Dstr *ha2;\n   Dstr *result;\n\n   if (realm->qop == QOPNOTSET || realm->qop == AUTH) {\n      /* A2 = Method \":\" digest-uri-value */\n      Dstr *a2 = dStr_new(NULL);\n      dStr_sprintf(a2, \"%s:%s\", method, digest_uri);\n      ha2 = md5hexdigest(a2);\n      dStr_free(a2, 1);\n   } else if (realm->qop == AUTHINT) {\n      /* A2 = Method \":\" digest-uri-value \":\" H(entity-body) */\n      Dstr *hentity = md5hexdigest(entity_body);\n      Dstr *a2 = dStr_new(NULL);\n      dStr_sprintf(a2, \"%s:%s:%s\", method, digest_uri, hentity->str);\n      ha2 = md5hexdigest(a2);\n      dStr_free(hentity, 1);\n      dStr_free(a2, 1);\n   } else {\n      MSG(\"a_Digest_create_auth: Unknown qop value.\\n\");\n      return NULL;\n   }\n   result = dStr_new(NULL);\n\n   if (realm->qop == AUTH || realm->qop == AUTHINT) {\n      dStr_sprintf(result,\n                   \"%s:%s:%08x:%s:%s:%s\",\n                   realm->authorization,\n                   realm->nonce,\n                   realm->nonce_count,\n                   realm->cnonce,\n                   QOP2STR[realm->qop],\n                   ha2->str);\n   } else {\n      dStr_sprintf(result,\n                   \"%s:%s:%s\",\n                   realm->authorization,\n                   realm->nonce,\n                   ha2->str);\n   }\n\n   Dstr *request_digest = md5hexdigest(result);\n   dStr_free(result, 1);\n   dStr_free(ha2, 1);\n   return request_digest;\n}\n\nstatic void Digest_Dstr_append_token_value(Dstr *str, int delimiter,\n                                           const char *token,\n                                           const char *value, int quoted)\n{\n   char c;\n   dStr_sprintfa(str, \"%s%s=\", (delimiter ? \", \" : \"\"), token);\n   if (quoted) {\n      dStr_append_c(str, '\"');\n      while ((c = *value++)) {\n         if (c == '\"')\n            dStr_append_c(str, '\\\\');\n         dStr_append_c(str, c);\n      }\n      dStr_append_c(str, '\"');\n   } else {\n      dStr_append(str, value);\n   }\n}\n\n/*\n * Construct Digest Authorization header.\n *\n * Field ordering: furaisanjin reports that his DVD recorder requires the\n * order that IE happens to use: \"username, realm, nonce, uri, cnonce, nc,\n * algorithm, response, qop\". It apparently doesn't use \"opaque\", so that's\n * been left where it already was.\n */\nchar *a_Digest_authorization_hdr(AuthRealm_t *realm, const DilloUrl *url,\n                                 const char *digest_uri)\n{\n   char *ret;\n   Dstr *response, *result;\n   const char *method = URL_FLAGS(url) & URL_Post ? \"POST\" : \"GET\";\n\n   realm->nonce_count++;\n   response = Digest_create_response(realm, method, digest_uri, URL_DATA(url));\n   if (!response)\n      return NULL;\n   result = dStr_new(\"Authorization: Digest \");\n   Digest_Dstr_append_token_value(result, 0, \"username\", realm->username, 1);\n   Digest_Dstr_append_token_value(result, 1, \"realm\", realm->name, 1);\n   Digest_Dstr_append_token_value(result, 1, \"nonce\", realm->nonce, 1);\n   Digest_Dstr_append_token_value(result, 1, \"uri\", digest_uri, 1);\n   if (realm->qop != QOPNOTSET) {\n      Digest_Dstr_append_token_value(result, 1, \"cnonce\", realm->cnonce, 1);\n      dStr_sprintfa(result, \", nc=%08x\", realm->nonce_count);\n   }\n   if (realm->algorithm != ALGORITHMNOTSET) {\n      Digest_Dstr_append_token_value(result, 1, \"algorithm\",\n                                     ALGORITHM2STR[realm->algorithm], 0);\n   }\n   Digest_Dstr_append_token_value(result, 1, \"response\", response->str, 1);\n   if (realm->opaque)\n      Digest_Dstr_append_token_value(result, 1, \"opaque\", realm->opaque, 1);\n   if (realm->qop != QOPNOTSET)\n      Digest_Dstr_append_token_value(result, 1, \"qop\", QOP2STR[realm->qop], 1);\n   dStr_sprintfa(result, \"\\r\\n\");\n\n   dStr_free(response, 1);\n   ret = result->str;\n   dStr_free(result, 0);\n   return ret;\n}\n"
  },
  {
    "path": "src/digest.h",
    "content": "#ifndef __DIGEST_H__\n#define __DIGEST_H__\n\n#include \"auth.h\"\n#include \"../dlib/dlib.h\"\n\n\nchar *a_Digest_create_cnonce(void);\nint a_Digest_compute_digest(AuthRealm_t *realm,\n                            const char *username,\n                            const char *passwd);\nchar *a_Digest_authorization_hdr(AuthRealm_t *realm,\n                                 const DilloUrl *url,\n                                 const char *uri);\n\n#endif /* !__DIGEST_H__ */\n"
  },
  {
    "path": "src/dillo-plus.cc",
    "content": "/*\n * Dillo web browser\n *\n * Copyright 1999-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <stdio.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <time.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <signal.h>\n#include <locale.h>\n#include <errno.h>\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n#include <FL/fl_ask.H>\n#include <FL/fl_draw.H>\n\n#include \"msg.h\"\n#include \"paths.hh\"\n#include \"uicmd.hh\"\n\n#include \"prefs.h\"\n#include \"prefsparser.hh\"\n#include \"keys.hh\"\n#include \"bw.h\"\n#include \"misc.h\"\n#include \"history.h\"\n\n#include \"dns.h\"\n#include \"web.hh\"\n#include \"IO/tls.h\"\n#include \"IO/Url.h\"\n#include \"IO/mime.h\"\n#include \"capi.h\"\n#include \"dicache.h\"\n#include \"cookies.h\"\n#include \"hsts.h\"\n#include \"domain.h\"\n#include \"auth.h\"\n#include \"styleengine.hh\"\n\n#include \"dw/fltkcore.hh\"\n#include \"dw/widget.hh\"\n#include \"dw/textblock.hh\"\n#include \"dw/table.hh\"\n\n/*\n * Command line options structure\n */\ntypedef enum {\n   DILLO_CLI_NONE          = 0,\n   DILLO_CLI_XID           = 1 << 0,\n   DILLO_CLI_FULLWINDOW    = 1 << 1,\n   DILLO_CLI_HELP          = 1 << 2,\n   DILLO_CLI_VERSION       = 1 << 3,\n   DILLO_CLI_LOCAL         = 1 << 4,\n   DILLO_CLI_GEOMETRY      = 1 << 5,\n   DILLO_CLI_ERROR         = 1 << 15,\n} OptID;\n\ntypedef struct {\n   const char *shortopt;\n   const char *longopt;\n   int opt_argc; /* positive: mandatory, negative: optional */\n   OptID id;\n   const char *help;\n} CLI_options;\n\nstatic const CLI_options Options[] = {\n   {\"-f\", \"--fullwindow\", 0, DILLO_CLI_FULLWINDOW,\n    \"  -f, --fullwindow       Start in full window mode: hide address bar,\\n\"\n    \"                         navigation buttons, menu, and status bar.\"},\n   {\"-g\", \"-geometry\",    1, DILLO_CLI_GEOMETRY,\n    \"  -g, -geometry GEO      Set initial window position where GEO is\\n\"\n    \"                         WxH[{+-}X{+-}Y]\"},\n   {\"-h\", \"--help\",       0, DILLO_CLI_HELP,\n    \"  -h, --help             Display this help text and exit.\"},\n   {\"-l\", \"--local\",      0, DILLO_CLI_LOCAL,\n    \"  -l, --local            Don't load images or stylesheets, or follow\\n\"\n    \"                         redirections, for these FILEs or URLs.\"},\n   {\"-v\", \"--version\",    0, DILLO_CLI_VERSION,\n    \"  -v, --version          Display version info and exit.\"},\n   {\"-x\", \"--xid\",        1, DILLO_CLI_XID,\n    \"  -x, --xid XID          Open first Dillo window in an existing\\n\"\n    \"                         window whose window ID is XID.\"},\n   {NULL, NULL, 0, DILLO_CLI_NONE, NULL}\n};\n\n\n/*\n * SIGCHLD handling ----------------------------------------------------------\n */\n\n/*\n * Avoid our children (Dpid) to become zombies. :-)\n * Notes:\n *   . We let sigaction block SIGCHLD while in the handler.\n *   . Portability is not simple. e.g.\n *       http://www.faqs.org/faqs/unix-faq/faq/part3/section-13.html\n *       man sigaction, waitpid\n */\nstatic void raw_sigchld2(int signum)\n{\n   pid_t pid;\n   int status;\n\n   while (1) {\n      pid = waitpid(-1, &status, WNOHANG);\n      if (pid > 0) {\n         if (WIFEXITED(status))        /* normal exit */\n            printf(\"[dpid]: terminated normally (%d)\\n\", WEXITSTATUS(status));\n         else if (WIFSIGNALED(status)) /* terminated by signal */\n            printf(\"[dpid]: terminated by signal %d\\n\", WTERMSIG(status));\n      } else if (pid == 0 || errno == ECHILD) {\n         break;\n      } else {\n         if (errno == EINTR)\n            continue;\n         perror(\"waitpid\");\n         break;\n      }\n   }\n   ++signum; /* compiler happiness */\n}\n\n/*\n * Establish SIGCHLD handler\n */\nstatic void est_sigchld(void)\n{\n   struct sigaction sigact;\n   sigset_t set;\n\n   (void) sigemptyset(&set);\n   sigact.sa_handler = raw_sigchld2; /* our custom handler */\n   sigact.sa_mask = set;             /* no aditional signal blocks */\n   sigact.sa_flags = SA_NOCLDSTOP;   /* ignore stop/resume states */\n   if (sigaction(SIGCHLD, &sigact, NULL) == -1) {\n      perror(\"sigaction\");\n      exit(1);\n   }\n}\n\n//----------------------------------------------------------------------------\n\n/*\n * Print help text generated from the options structure\n */\nstatic void printHelp(const char *cmdname, const CLI_options *options)\n{\n   printf(\"Usage: %s [OPTION]... [--] [URL|FILE]...\\n\"\n          \"Options:\\n\", cmdname);\n   while (options && options->help) {\n      printf(\"%s\\n\", options->help);\n      options++;\n   }\n   printf(\"  URL                    URL to browse.\\n\"\n          \"  FILE                   Local FILE to view.\\n\"\n          \"\\n\");\n}\n\n/*\n * Return the maximum number of option arguments\n */\nstatic int numOptions(const CLI_options *options)\n{\n   int i, max;\n\n   for (i = 0, max = 0; options[i].shortopt; i++)\n      if (abs(options[i].opt_argc) > max)\n         max = abs(options[i].opt_argc);\n   return max;\n}\n\n/*\n * Get next command line option.\n */\nstatic OptID getCmdOption(const CLI_options *options, int argc, char **argv,\n                           char **opt_argv, int *idx)\n{\n   typedef enum { O_SEARCH, O_FOUND, O_NOTFOUND, O_DONE } State;\n   OptID opt_id = DILLO_CLI_NONE;\n   int i = 0;\n   State state = O_SEARCH;\n\n   if (*idx >= argc) {\n      state = O_DONE;\n   } else {\n      state = O_NOTFOUND;\n      for (i = 0; options[i].shortopt; i++) {\n         if (strcmp(options[i].shortopt, argv[*idx]) == 0 ||\n             strcmp(options[i].longopt, argv[*idx]) == 0) {\n            state = O_FOUND;\n            ++*idx;\n            break;\n         }\n      }\n   }\n   if (state == O_FOUND) {\n      int n_arg = options[i].opt_argc;\n      opt_id = options[i].id;\n      /* Find the required/optional arguments of the option */\n      for (i = 0; *idx < argc && i < abs(n_arg) && argv[*idx][0] != '-'; i++)\n         opt_argv[i] = argv[(*idx)++];\n      opt_argv[i] = NULL;\n\n      /* Optional arguments have opt_argc < 0 */\n      if (i < n_arg) {\n         fprintf(stderr, \"Option %s requires %d argument%s\\n\",\n                 argv[*idx-i-1], n_arg, (n_arg == 1) ? \"\" : \"s\");\n         opt_id = DILLO_CLI_ERROR;\n      }\n   }\n   if (state == O_NOTFOUND) {\n      if (strcmp(argv[*idx], \"--\") == 0)\n         (*idx)++;\n      else if (argv[*idx][0] == '-') {\n         fprintf(stderr, \"Command line option \\\"%s\\\" not recognized.\\n\",\n                 argv[*idx]);\n         opt_id = DILLO_CLI_ERROR;\n      }\n   }\n   return opt_id;\n}\n\n/*\n * Set FL_NORMAL_LABEL to interpret neither symbols (@) nor shortcuts (&),\n * and FL_FREE_LABELTYPE to interpret shortcuts.\n */\nstatic void custLabelDraw(const Fl_Label* o, int X, int Y, int W, int H,\n                          Fl_Align align)\n{\n   const int interpret_symbols = 0;\n\n   fl_draw_shortcut = 0;\n   fl_font(o->font, o->size);\n   fl_color((Fl_Color)o->color);\n   fl_draw(o->value, X, Y, W, H, align, o->image, interpret_symbols);\n}\n\nstatic void custLabelMeasure(const Fl_Label* o, int& W, int& H)\n{\n   const int interpret_symbols = 0;\n\n   fl_draw_shortcut = 0;\n   fl_font(o->font, o->size);\n   fl_measure(o->value, W, H, interpret_symbols);\n}\n\nstatic void custMenuLabelDraw(const Fl_Label* o, int X, int Y, int W, int H,\n                              Fl_Align align)\n{\n   const int interpret_symbols = 0;\n\n   fl_draw_shortcut = 1;\n   fl_font(o->font, o->size);\n   fl_color((Fl_Color)o->color);\n   fl_draw(o->value, X, Y, W, H, align, o->image, interpret_symbols);\n}\n\nstatic void custMenuLabelMeasure(const Fl_Label* o, int& W, int& H)\n{\n   const int interpret_symbols = 0;\n\n   fl_draw_shortcut = 1;\n   fl_font(o->font, o->size);\n   fl_measure(o->value, W, H, interpret_symbols);\n}\n\n/*\n * Tell the user if default/pref fonts can't be found.\n */\nstatic void checkFont(const char *name, const char *type)\n{\n   if (! dw::fltk::FltkFont::fontExists(name))\n      MSG_WARN(\"preferred %s font \\\"%s\\\" not found.\\n\", type, name);\n}\n\nstatic void checkPreferredFonts()\n{\n   checkFont(prefs.font_sans_serif, \"sans-serif\");\n   checkFont(prefs.font_serif, \"serif\");\n   checkFont(prefs.font_monospace, \"monospace\");\n   checkFont(prefs.font_cursive, \"cursive\");\n   checkFont(prefs.font_fantasy, \"fantasy\");\n}\n\n/*\n * Set UI color. 'color' is an 0xrrggbb value, whereas 'default_val' is a fltk\n * color (index 0-0xFF, or 0xrrggbb00).\n */\nstatic void setUIColorWdef(Fl_Color idx, int32_t color, Fl_Color default_val)\n{\n   if (color != -1)\n      Fl::set_color(idx, color << 8);\n   else if (default_val != 0xFFFFFFFF)\n      Fl::set_color(idx, default_val);\n}\n\nstatic void setColors()\n{\n   /* The main background is a special case because Fl::background() will\n    * set the \"gray ramp\", which is a set of lighter and darker colors based\n    * on the main background and used for box edges and such.\n    */\n   if (prefs.ui_main_bg_color != -1) {\n      uchar r = prefs.ui_main_bg_color >> 16,\n            g = prefs.ui_main_bg_color >> 8 & 0xff,\n            b = prefs.ui_main_bg_color & 0xff;\n\n      Fl::background(r, g, b);\n   }\n\n   setUIColorWdef(FL_BACKGROUND2_COLOR, prefs.ui_text_bg_color, 0xFFFFFFFF);\n   setUIColorWdef(FL_FOREGROUND_COLOR, prefs.ui_fg_color, 0xFFFFFFFF);\n   setUIColorWdef(FL_SELECTION_COLOR, prefs.ui_selection_color,\n                  fl_contrast(FL_SELECTION_COLOR, FL_BACKGROUND2_COLOR));\n   setUIColorWdef(PREFS_UI_BUTTON_HIGHLIGHT_COLOR,\n                  prefs.ui_button_highlight_color,\n                  fl_lighter(FL_BACKGROUND_COLOR));\n   setUIColorWdef(PREFS_UI_TAB_ACTIVE_BG_COLOR, prefs.ui_tab_active_bg_color,\n                  Fl::get_color(FL_BACKGROUND2_COLOR));\n   setUIColorWdef(PREFS_UI_TAB_BG_COLOR, prefs.ui_tab_bg_color,\n                  Fl::get_color(FL_BACKGROUND_COLOR));\n   setUIColorWdef(PREFS_UI_TAB_ACTIVE_FG_COLOR, prefs.ui_tab_active_fg_color,\n                  Fl::get_color(FL_FOREGROUND_COLOR));\n   setUIColorWdef(PREFS_UI_TAB_FG_COLOR, prefs.ui_tab_fg_color,\n                  Fl::get_color(FL_FOREGROUND_COLOR));\n}\n\n/*\n * Given a command line argument, build a DilloUrl for it.\n */\nstatic DilloUrl *makeStartUrl(char *str, bool local)\n{\n   char *url_str, *p;\n   DilloUrl *start_url;\n\n   /* Relative path to a local file? */\n   p = (*str == '/') ? dStrdup(str) :\n                       dStrconcat(Paths::getOldWorkingDir(), \"/\", str, NULL);\n\n   if (access(p, F_OK) == 0) {\n      /* absolute path may have non-URL characters */\n      url_str = a_Misc_escape_chars(p, \"% #\");\n      start_url = a_Url_new(url_str + 1, \"file:/\");\n   } else {\n      /* Not a file, filter URL string */\n      url_str = a_Url_string_strip_delimiters(str);\n      start_url = a_Url_new(url_str, NULL);\n   }\n   dFree(p);\n   dFree(url_str);\n\n   if (local)\n      a_Url_set_flags(start_url, URL_FLAGS(start_url) | URL_SpamSafe);\n\n   return start_url;\n}\n\n/*\n * MAIN\n */\nint main(int argc, char **argv)\n{\n   uint_t opt_id;\n   uint_t options_got = 0;\n   uint32_t xid = 0;\n   int idx = 1;\n   int xpos = PREFS_GEOMETRY_DEFAULT_XPOS, ypos = PREFS_GEOMETRY_DEFAULT_YPOS,\n       width = PREFS_GEOMETRY_DEFAULT_WIDTH,\n       height = PREFS_GEOMETRY_DEFAULT_HEIGHT;\n   char **opt_argv;\n   FILE *fp;\n\n   srand((uint_t)(time(0) ^ getpid()));\n\n   // Some OSes exit dillo without this (not GNU/Linux).\n   signal(SIGPIPE, SIG_IGN);\n   // Establish our custom SIGCHLD handler\n   est_sigchld();\n\n   /* Handle command line options */\n   opt_argv = dNew0(char*, numOptions(Options) + 1);\n   while ((opt_id = getCmdOption(Options, argc, argv, opt_argv, &idx))) {\n      options_got |= opt_id;\n      switch (opt_id) {\n      case DILLO_CLI_FULLWINDOW:\n      case DILLO_CLI_LOCAL:\n         break;\n      case DILLO_CLI_XID:\n      {\n         char *end;\n         xid = strtol(opt_argv[0], &end, 0);\n         if (*end) {\n            fprintf(stderr, \"XID argument \\\"%s\\\" not valid.\\n\",opt_argv[0]);\n            return 2;\n         }\n         break;\n      }\n      case DILLO_CLI_GEOMETRY:\n         if (!a_Misc_parse_geometry(opt_argv[0],&xpos,&ypos,&width,&height)){\n            fprintf(stderr, \"geometry argument \\\"%s\\\" not valid. Must be of \"\n                            \"the form WxH[{+-}X{+-}Y].\\n\", opt_argv[0]);\n            return 2;\n         }\n         break;\n      case DILLO_CLI_VERSION:\n         puts(\"Dillo version \" VERSION);\n         return 0;\n      case DILLO_CLI_HELP:\n         printHelp(argv[0], Options);\n         return 0;\n      default:\n         printHelp(argv[0], Options);\n         return 2;\n      }\n   }\n   dFree(opt_argv);\n\n   // set the default values for the preferences\n   a_Prefs_init();\n\n   // create ~/.dillo if not present\n   Paths::init();\n\n   // initialize default key bindings\n   Keys::init();\n\n   // parse dillorc\n   if ((fp = Paths::getPrefsFP(PATHS_RC_PREFS))) {\n      PrefsParser::parse(fp);\n   }\n   // parse bm.txt\n   if ((fp = Paths::getPrefsFP(PATHS_BM))) {\n   }\n   // parse keysrc\n   if ((fp = Paths::getPrefsFP(PATHS_RC_KEYS))) {\n      Keys::parse(fp);\n   }\n   // parse domainrc\n   if ((fp = Paths::getPrefsFP(PATHS_RC_DOMAIN))) {\n      a_Domain_parse(fp);\n      fclose(fp);\n   }\n   dLib_show_messages(prefs.show_msg);\n\n   // initialize internal modules\n   a_Dpi_init();\n   a_Dns_init();\n   a_Web_init();\n   a_Http_init();\n   a_Tls_init();\n   a_Mime_init();\n   a_Capi_init();\n   a_Dicache_init();\n   a_Bw_init();\n   a_Cookies_init();\n   a_Hsts_init(Paths::getPrefsFP(PATHS_HSTS_PRELOAD));\n   a_Auth_init();\n   a_UIcmd_init();\n   StyleEngine::init();\n\n   dw::core::Widget::setAdjustMinWidth (prefs.adjust_min_width);\n   dw::Table::setAdjustTableMinWidth (prefs.adjust_table_min_width);\n   dw::Textblock::setPenaltyHyphen (prefs.penalty_hyphen);\n   dw::Textblock::setPenaltyHyphen2 (prefs.penalty_hyphen_2);\n   dw::Textblock::setPenaltyEmDashLeft (prefs.penalty_em_dash_left);\n   dw::Textblock::setPenaltyEmDashRight (prefs.penalty_em_dash_right);\n   dw::Textblock::setPenaltyEmDashRight2 (prefs.penalty_em_dash_right_2);\n   dw::Textblock::setStretchabilityFactor (prefs.stretchability_factor);\n\n   /* command line options override preferences */\n   if (options_got & DILLO_CLI_FULLWINDOW)\n      prefs.fullwindow_start = TRUE;\n   if (options_got & DILLO_CLI_GEOMETRY) {\n       prefs.width = width;\n       prefs.height = height;\n       prefs.xpos = xpos;\n       prefs.ypos = ypos;\n   }\n\n   // Sets WM_CLASS hint on X11\n   Fl_Window::default_xclass(\"dillo\");\n\n   Fl::scheme(prefs.theme);\n\n   // Disable drag and drop as it crashes on MacOSX\n   Fl::dnd_text_ops(0);\n\n   setColors();\n\n   if (!prefs.show_ui_tooltip) {\n      Fl::option(Fl::OPTION_SHOW_TOOLTIPS, false);\n   }\n\n   // Disable '@' and '&' interpretation in normal labels.\n   Fl::set_labeltype(FL_NORMAL_LABEL, custLabelDraw, custLabelMeasure);\n\n   // Use to permit '&' interpretation.\n   Fl::set_labeltype(FL_FREE_LABELTYPE,custMenuLabelDraw,custMenuLabelMeasure);\n\n   checkPreferredFonts();\n\n   /* use preferred font for UI */\n   Fl_Font defaultFont = dw::fltk::FltkFont::get (prefs.font_sans_serif, 0);\n   Fl::set_font(FL_HELVETICA, defaultFont); // this seems to be the\n                                            // only way to set the\n                                            // default font in fltk1.3\n\n   fl_message_title_default(\"Dillo: Message\");\n\n   // Create a new UI/bw pair\n   BrowserWindow *bw = a_UIcmd_browser_window_new(0, 0, xid, NULL);\n\n   /* We need this so that fl_text_extents() in dw/fltkplatform.cc can\n    * work when FLTK is configured without XFT and Dillo is opening\n    * immediately-available URLs from the cmdline (e.g. about:splash).\n    */\n   ((Fl_Widget *)bw->ui)->window()->make_current();\n\n   /* Proxy authentication */\n   if (prefs.http_proxyuser && !a_Http_proxy_auth()) {\n      const char *passwd = a_UIcmd_get_passwd(prefs.http_proxyuser);\n      if (passwd) {\n         a_Http_set_proxy_passwd(passwd);\n      } else {\n         MSG_WARN(\"Not using proxy authentication.\\n\");\n      }\n   }\n\n   /* Open URLs/files */\n   const bool local = options_got & DILLO_CLI_LOCAL;\n\n   if (idx == argc) {\n      /* No URLs/files on cmdline. Send startup screen */\n      if (dStrAsciiCasecmp(URL_SCHEME(prefs.start_page), \"about\") == 0 &&\n          strcmp(URL_PATH(prefs.start_page), \"blank\") == 0)\n         a_UIcmd_open_url(bw, NULL); // NULL URL focuses location\n      else {\n         a_UIcmd_open_url(bw, prefs.start_page);\n         a_UIcmd_set_location_text(bw, URL_STR(prefs.start_page));\n      }\n   } else {\n      for (int i = idx; i < argc; i++) {\n         DilloUrl *start_url = makeStartUrl(argv[i], local);\n\n         if (i > idx) {\n            if (prefs.middle_click_opens_new_tab) {\n               /* user must prefer tabs */\n               const int focus = 1;\n               a_UIcmd_open_url_nt(bw, start_url, focus);\n            } else {\n               a_UIcmd_open_url_nw(bw, start_url);\n            }\n         } else {\n            a_UIcmd_open_url(bw, start_url);\n            a_UIcmd_set_location_text(bw, URL_STR(start_url));\n         }\n         a_Url_free(start_url);\n      }\n   }\n\n   Fl::run();\n\n   /*\n    * Memory deallocating routines\n    * (This can be left to the OS, but we'll do it, with a view to test\n    *  and fix our memory management)\n    */\n   a_Domain_freeall();\n   a_Cookies_freeall();\n   a_Hsts_freeall();\n   a_Cache_freeall();\n   a_Dicache_freeall();\n   a_Http_freeall();\n   a_Tls_freeall();\n   a_Dns_freeall();\n   a_History_freeall();\n   a_Prefs_freeall();\n   Keys::free();\n   Paths::free();\n   dw::core::freeall();\n   dw::fltk::freeall();\n   /* TODO: auth, css */\n\n   //a_Dpi_dillo_exit();\n   MSG(\"Dillo: normal exit!\\n\");\n   return 0;\n}\n"
  },
  {
    "path": "src/djpeg.h",
    "content": "#ifndef __JPEG_H__\n#define __JPEG_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#include \"url.h\"\n#include \"image.hh\"\n\n\nvoid *a_Jpeg_new(DilloImage *Image, DilloUrl *url, int version);\nvoid a_Jpeg_callback(int Op, void *data);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* !__JPEG_H__ */\n"
  },
  {
    "path": "src/dns.c",
    "content": "/*\n * File: dns.c\n *\n * Copyright (C) 1999-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Non blocking pthread-handled Dns scheme\n */\n\n\n/*\n * Uncomment the following line for debugging or gprof profiling.\n */\n/* #undef D_DNS_THREADED */\n\n#ifdef D_DNS_THREADED\n#  include <pthread.h>\n#endif\n\n\n#include <assert.h>\n#include <netdb.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <unistd.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"msg.h\"\n#include \"dns.h\"\n#include \"list.h\"\n#include \"IO/iowatch.hh\"\n\n\n/* Maximum dns resolving threads */\n#ifdef D_DNS_THREADED\n#  define D_DNS_MAX_SERVERS 4\n#else\n#  define D_DNS_MAX_SERVERS 1\n#endif\n\ntypedef enum {\n   DNS_SERVER_IDLE,\n   DNS_SERVER_PROCESSING,\n   DNS_SERVER_RESOLVED,\n} DnsServerState_t;\n\ntypedef struct {\n   int channel;            /* Index of this channel [0 based] */\n   DnsServerState_t state;\n   Dlist *addr_list;       /* IP address */\n   char *hostname;         /* Address to resolve */\n   int status;             /* errno code for resolving function */\n#ifdef D_DNS_THREADED\n   pthread_t th1;          /* Thread id */\n#endif\n} DnsServer;\n\ntypedef struct {\n   char *hostname;         /* host name for cache */\n   Dlist *addr_list;       /* addresses of host */\n} GDnsCache;\n\ntypedef struct {\n   int channel;            /* -2 if waiting, otherwise index to dns_server[] */\n   char *hostname;         /* The one we're resolving */\n   DnsCallback_t cb_func;  /* callback function */\n   void *cb_data;          /* extra data for the callback function */\n} GDnsQueue;\n\n\n/*\n * Forward declarations\n */\nstatic void Dns_timeout_client(int fd, void *data);\n\n/*\n * Local Data\n */\nstatic DnsServer dns_server[D_DNS_MAX_SERVERS];\nstatic int num_servers;\nstatic GDnsCache *dns_cache;\nstatic int dns_cache_size, dns_cache_size_max;\nstatic GDnsQueue *dns_queue;\nstatic int dns_queue_size, dns_queue_size_max;\nstatic int dns_notify_pipe[2];\n\n\n/* ----------------------------------------------------------------------\n *  Dns queue functions\n */\nstatic void Dns_queue_add(int channel, const char *hostname,\n                          DnsCallback_t cb_func, void *cb_data)\n{\n   a_List_add(dns_queue, dns_queue_size, dns_queue_size_max);\n   dns_queue[dns_queue_size].channel = channel;\n   dns_queue[dns_queue_size].hostname = dStrdup(hostname);\n   dns_queue[dns_queue_size].cb_func = cb_func;\n   dns_queue[dns_queue_size].cb_data = cb_data;\n   dns_queue_size++;\n}\n\n/*\n * Find hostname index in dns_queue\n * (if found, returns queue index; -1 if not)\n */\nstatic int Dns_queue_find(const char *hostname)\n{\n   int i;\n\n   for (i = 0; i < dns_queue_size; i++)\n      if (!dStrAsciiCasecmp(hostname, dns_queue[i].hostname))\n         return i;\n\n   return -1;\n}\n\n/*\n * Given an index, remove an entry from the dns_queue\n */\nstatic void Dns_queue_remove(int index)\n{\n   int i;\n\n   _MSG(\"Dns_queue_remove: deleting client [%d] [queue_size=%d]\\n\",\n        index, dns_queue_size);\n\n   if (index < dns_queue_size) {\n      dFree(dns_queue[index].hostname);\n      --dns_queue_size;         /* you'll find out why ;-) */\n      for (i = index; i < dns_queue_size; i++)\n         dns_queue[i] = dns_queue[i + 1];\n   }\n}\n\n/*\n * Debug function\n *\nvoid Dns_queue_print()\n{\n   int i;\n\n   MSG(\"Queue: [\");\n   for (i = 0; i < dns_queue_size; i++)\n      MSG(\"%d:%s \", dns_queue[i].channel, dns_queue[i].hostname);\n   MSG(\"]\\n\");\n}\n */\n\n/*\n *  Add an IP/hostname pair to Dns-cache\n */\nstatic void Dns_cache_add(char *hostname, Dlist *addr_list)\n{\n   a_List_add(dns_cache, dns_cache_size, dns_cache_size_max);\n   dns_cache[dns_cache_size].hostname = dStrdup(hostname);\n   dns_cache[dns_cache_size].addr_list = addr_list;\n   ++dns_cache_size;\n   _MSG(\"Cache objects: %d\\n\", dns_cache_size);\n}\n\n\n/*\n *  Initializer function\n */\nvoid a_Dns_init(void)\n{\n   int res, i;\n\n#ifdef D_DNS_THREADED\n   MSG(\"dillo_dns_init: Here we go! (threaded)\\n\");\n#else\n   MSG(\"dillo_dns_init: Here we go! (not threaded)\\n\");\n#endif\n\n   dns_queue_size = 0;\n   dns_queue_size_max = 16;\n   dns_queue = dNew(GDnsQueue, dns_queue_size_max);\n\n   dns_cache_size = 0;\n   dns_cache_size_max = 16;\n   dns_cache = dNew(GDnsCache, dns_cache_size_max);\n\n   num_servers = D_DNS_MAX_SERVERS;\n\n   res = pipe(dns_notify_pipe);\n   assert(res == 0);\n   fcntl(dns_notify_pipe[0], F_SETFL, O_NONBLOCK);\n   a_IOwatch_add_fd(dns_notify_pipe[0], DIO_READ, Dns_timeout_client, NULL);\n\n   /* Initialize servers data */\n   for (i = 0; i < num_servers; ++i) {\n      dns_server[i].channel = i;\n      dns_server[i].state = DNS_SERVER_IDLE;\n      dns_server[i].addr_list = NULL;\n      dns_server[i].hostname = NULL;\n      dns_server[i].status = 0;\n#ifdef D_DNS_THREADED\n      dns_server[i].th1 = (pthread_t) -1;\n#endif\n   }\n}\n\n/*\n * Allocate a host structure and add it to the list\n */\n\nstatic void Dns_note_hosts(Dlist *list, struct addrinfo *res0)\n{\n   struct addrinfo *res;\n   DilloHost *dh;\n\n   for (res = res0; res; res = res->ai_next) {\n\n      if (res->ai_family == AF_INET) {\n         struct sockaddr_in *in_addr;\n\n         if (res->ai_addrlen < sizeof(struct sockaddr_in)) {\n            continue;\n         }\n\n         dh = dNew0(DilloHost, 1);\n         dh->af = AF_INET;\n\n         in_addr = (struct sockaddr_in*) res->ai_addr;\n         dh->alen = sizeof (struct in_addr);\n         memcpy(&dh->data[0], &in_addr->sin_addr.s_addr, dh->alen);\n\n         dList_append(list, dh);\n#ifdef ENABLE_IPV6\n      } else if (res->ai_family == AF_INET6) {\n         struct sockaddr_in6 *in6_addr;\n\n         if (res->ai_addrlen < sizeof(struct sockaddr_in6)) {\n            continue;\n         }\n\n         dh = dNew0(DilloHost, 1);\n         dh->af = AF_INET6;\n\n         in6_addr = (struct sockaddr_in6*) res->ai_addr;\n         dh->alen = sizeof (struct in6_addr);\n         memcpy(&dh->data[0], &in6_addr->sin6_addr.s6_addr, dh->alen);\n\n         dList_append(list, dh);\n#endif\n      }\n   }\n}\n\n/*\n *  Server function (runs on its own thread)\n */\nstatic void *Dns_server(void *data)\n{\n   int channel = VOIDP2INT(data);\n   struct addrinfo hints, *res0;\n   int error;\n   Dlist *hosts;\n   size_t length, i;\n   char addr_string[40];\n\n   memset(&hints, 0, sizeof(hints));\n#ifdef ENABLE_IPV6\n   hints.ai_family = AF_UNSPEC;\n#else\n   hints.ai_family = AF_INET;\n#endif\n   hints.ai_socktype = SOCK_STREAM;\n\n   hosts = dList_new(2);\n\n   _MSG(\"Dns_server: starting...\\n ch: %d host: %s\\n\",\n        channel, dns_server[channel].hostname);\n\n   error = getaddrinfo(dns_server[channel].hostname, NULL, &hints, &res0);\n\n   if (error != 0) {\n      dns_server[channel].status = error;\n      if (error == EAI_NONAME)\n         MSG(\"DNS error: HOST_NOT_FOUND\\n\");\n      else if (error == EAI_AGAIN)\n         MSG(\"DNS error: TRY_AGAIN\\n\");\n#ifdef EAI_NODATA\n      /* Some FreeBSD don't have this anymore */\n      else if (error == EAI_NODATA)\n         MSG(\"DNS error: NO_ADDRESS\\n\");\n#endif\n      else if (h_errno == EAI_FAIL)\n         MSG(\"DNS error: NO_RECOVERY\\n\");\n   } else {\n      Dns_note_hosts(hosts, res0);\n      dns_server[channel].status = 0;\n      freeaddrinfo(res0);\n   }\n\n   if (dList_length(hosts) > 0) {\n      dns_server[channel].status = 0;\n   } else {\n      dList_free(hosts);\n      hosts = NULL;\n   }\n\n   /* tell our findings */\n   MSG(\"Dns_server [%d]: %s is\", channel,\n       dns_server[channel].hostname);\n   if ((length = dList_length(hosts))) {\n      for (i = 0; i < length; i++) {\n         a_Dns_dillohost_to_string(dList_nth_data(hosts, i),\n                                   addr_string, sizeof(addr_string));\n         MSG(\" %s\", addr_string);\n      }\n      MSG(\"\\n\");\n   } else {\n      MSG(\" (nil)\\n\");\n   }\n   dns_server[channel].addr_list = hosts;\n   dns_server[channel].state = DNS_SERVER_RESOLVED;\n\n   write(dns_notify_pipe[1], \".\", 1);\n\n   return NULL;                 /* (avoids a compiler warning) */\n}\n\n\n/*\n *  Request function (spawn a server and let it handle the request)\n */\nstatic void Dns_server_req(int channel, const char *hostname)\n{\n#ifdef D_DNS_THREADED\n   static pthread_attr_t thrATTR;\n   static int thrATTRInitialized = 0;\n#endif\n\n   dns_server[channel].state = DNS_SERVER_PROCESSING;\n\n   dFree(dns_server[channel].hostname);\n   dns_server[channel].hostname = dStrdup(hostname);\n\n#ifdef D_DNS_THREADED\n   /* set the thread attribute to the detached state */\n   if (!thrATTRInitialized) {\n      pthread_attr_init(&thrATTR);\n      pthread_attr_setdetachstate(&thrATTR, PTHREAD_CREATE_DETACHED);\n      thrATTRInitialized = 1;\n   }\n   /* Spawn thread */\n   pthread_create(&dns_server[channel].th1, &thrATTR, Dns_server,\n                  INT2VOIDP(dns_server[channel].channel));\n#else\n   Dns_server(0);\n#endif\n}\n\n/*\n * Return the IP for the given hostname using a callback.\n * Side effect: a thread is spawned when hostname is not cached.\n */\nvoid a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data)\n{\n   int i, channel;\n\n   if (!hostname)\n      return;\n\n   /* check for cache hit. */\n   for (i = 0; i < dns_cache_size; i++)\n      if (!dStrAsciiCasecmp(hostname, dns_cache[i].hostname))\n         break;\n\n   if (i < dns_cache_size) {\n      /* already resolved, call the Callback immediately. */\n      cb_func(0, dns_cache[i].addr_list, cb_data);\n\n   } else if ((i = Dns_queue_find(hostname)) != -1) {\n      /* hit in queue, but answer hasn't come back yet. */\n      Dns_queue_add(dns_queue[i].channel, hostname, cb_func, cb_data);\n\n   } else {\n      /* Never requested before -- we must resolve it! */\n\n      /* Find a channel we can send the request to */\n      for (channel = 0; channel < num_servers; channel++)\n         if (dns_server[channel].state == DNS_SERVER_IDLE)\n            break;\n      if (channel < num_servers) {\n         /* Found a free channel! */\n         Dns_queue_add(channel, hostname, cb_func, cb_data);\n         Dns_server_req(channel, hostname);\n      } else {\n         /* We'll have to wait for a thread to finish... */\n         Dns_queue_add(-2, hostname, cb_func, cb_data);\n      }\n   }\n}\n\n/*\n * Give answer to all queued callbacks on this channel\n */\nstatic void Dns_serve_channel(int channel)\n{\n   int i;\n   DnsServer *srv = &dns_server[channel];\n\n   for (i = 0; i < dns_queue_size; i++) {\n      if (dns_queue[i].channel == channel) {\n         dns_queue[i].cb_func(srv->status, srv->addr_list,\n                              dns_queue[i].cb_data);\n         Dns_queue_remove(i);\n         --i;\n      }\n   }\n}\n\n/*\n * Assign free channels to waiting clients (-2)\n */\nstatic void Dns_assign_channels(void)\n{\n   int ch, i, j;\n\n   for (ch = 0; ch < num_servers; ++ch) {\n      if (dns_server[ch].state == DNS_SERVER_IDLE) {\n         /* Find the next query in the queue (we're a FIFO) */\n         for (i = 0; i < dns_queue_size; i++)\n            if (dns_queue[i].channel == -2)\n               break;\n\n         if (i < dns_queue_size) {\n            /* assign this channel to every queued request\n             * with the same hostname*/\n            for (j = i; j < dns_queue_size; j++)\n               if (dns_queue[j].channel == -2 &&\n                   !dStrAsciiCasecmp(dns_queue[j].hostname,\n                                     dns_queue[i].hostname)) {\n                  dns_queue[j].channel = ch;\n               }\n            Dns_server_req(ch, dns_queue[i].hostname);\n         } else\n            return;\n      }\n   }\n}\n\n/*\n * This function is called on the main thread and\n * reads the DNS results.\n */\nstatic void Dns_timeout_client(int fd, void *data)\n{\n   int i;\n   char buf[16];\n\n   while (read(dns_notify_pipe[0], buf, sizeof(buf)) > 0);\n\n   for (i = 0; i < num_servers; ++i) {\n      DnsServer *srv = &dns_server[i];\n\n      if (srv->state == DNS_SERVER_RESOLVED) {\n         if (srv->addr_list != NULL) {\n            /* DNS succeeded, let's cache it */\n            Dns_cache_add(srv->hostname, srv->addr_list);\n         }\n         Dns_serve_channel(i);\n         srv->state = DNS_SERVER_IDLE;\n      }\n   }\n   Dns_assign_channels();\n}\n\n\n/*\n *  Dns memory-deallocation\n *  (Call this one at exit time)\n *  The Dns_queue is deallocated at execution time (no need to do that here)\n *  'dns_cache' is the only one that grows dynamically\n */\nvoid a_Dns_freeall(void)\n{\n   int i, j;\n\n   for ( i = 0; i < dns_cache_size; ++i ){\n      dFree(dns_cache[i].hostname);\n      for ( j = 0; j < dList_length(dns_cache[i].addr_list); ++j)\n         dFree(dList_nth_data(dns_cache[i].addr_list, j));\n      dList_free(dns_cache[i].addr_list);\n   }\n   a_IOwatch_remove_fd(dns_notify_pipe[0], DIO_READ);\n   dClose(dns_notify_pipe[0]);\n   dClose(dns_notify_pipe[1]);\n   dFree(dns_cache);\n}\n\n/*\n *  Writes a string representation of the given DilloHost\n *  into dst. dst will be \\0 terminated.\n *  Please note that dst must be at least 40 bytes long for IPv6\n *  addresses.\n */\nvoid a_Dns_dillohost_to_string(DilloHost *host, char *dst, size_t size)\n{\n   if (!inet_ntop(host->af, host->data, dst, size)) {\n      switch (errno) {\n         case EAFNOSUPPORT:\n            snprintf(dst, size, \"Unknown address family\");\n            break;\n         case ENOSPC:\n            snprintf(dst, size, \"Buffer too small\");\n            break;\n      }\n   }\n}\n"
  },
  {
    "path": "src/dns.h",
    "content": "#ifndef __DNS_H__\n#define __DNS_H__\n\n#include <netinet/in.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\ntypedef void (*DnsCallback_t)(int status, Dlist *addr_list, void *data);\n\nvoid a_Dns_init (void);\nvoid a_Dns_freeall(void);\nvoid a_Dns_resolve(const char *hostname, DnsCallback_t cb_func, void *cb_data);\n\n#ifdef ENABLE_IPV6\n#  define DILLO_ADDR_MAX sizeof(struct in6_addr)\n#else\n#  define DILLO_ADDR_MAX sizeof(struct in_addr)\n#endif\n\ntypedef struct {\n  int af;\n  int alen;\n  char data[DILLO_ADDR_MAX];\n} DilloHost;\n\nvoid a_Dns_dillohost_to_string(DilloHost *host, char *dst, size_t size);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __DNS_H__ */\n"
  },
  {
    "path": "src/doctree.hh",
    "content": "#ifndef __DOCTREE_HH__\n#define __DOCTREE_HH__\n\n#include \"lout/misc.hh\"\n\nclass DoctreeNode {\n   public:\n      DoctreeNode *parent;\n      DoctreeNode *sibling;\n      DoctreeNode *lastChild;\n      int num; // unique ascending id\n      int element;\n      lout::misc::SimpleVector<char*> *klass;\n      const char *pseudo;\n      const char *id;\n\n      DoctreeNode () {\n         parent = NULL;\n         sibling = NULL;\n         lastChild = NULL;\n         klass = NULL;\n         pseudo = NULL;\n         id = NULL;\n         element = 0;\n      };\n\n      ~DoctreeNode () {\n         dFree ((void*) id);\n         while (lastChild) {\n            DoctreeNode *n = lastChild;\n            lastChild = lastChild->sibling;\n            delete n;\n         }\n         if (klass) {\n            for (int i = 0; i < klass->size (); i++)\n               dFree (klass->get(i));\n            delete klass;\n         }\n      }\n};\n\n/**\n * \\brief HTML document tree interface.\n *\n * The Doctree class defines the interface to the parsed HTML document tree\n * as it is used for CSS selector matching.\n */\nclass Doctree {\n   private:\n      DoctreeNode *topNode;\n      DoctreeNode *rootNode;\n      int num;\n\n   public:\n      Doctree () {\n         rootNode = new DoctreeNode;\n         topNode = rootNode;\n         num = 0;\n      };\n\n      ~Doctree () {\n         delete rootNode;\n      };\n\n      DoctreeNode *push () {\n         DoctreeNode *dn = new DoctreeNode ();\n         dn->parent = topNode;\n         dn->sibling = dn->parent->lastChild;\n         dn->parent->lastChild = dn;\n         dn->num = num++;\n         topNode = dn;\n         return dn;\n      };\n\n      void pop () {\n         assert (topNode != rootNode); // never pop the root node\n         topNode = topNode->parent;\n      };\n\n      inline DoctreeNode *top () {\n         if (topNode != rootNode)\n            return topNode;\n         else\n            return NULL;\n      };\n\n      inline DoctreeNode *parent (const DoctreeNode *node) {\n         if (node->parent != rootNode)\n            return node->parent;\n         else\n            return NULL;\n      };\n\n      inline DoctreeNode *sibling (const DoctreeNode *node) {\n         return node->sibling;\n      };\n};\n\n#endif\n"
  },
  {
    "path": "src/domain.c",
    "content": "/*\n * File: domain.c\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <stdlib.h>\n\n#include \"../dlib/dlib.h\"\n#include \"msg.h\"\n#include \"list.h\"\n#include \"domain.h\"\n\ntypedef struct {\n   char *origin;\n   char *destination;\n} Rule;\n\nstatic Rule *exceptions = NULL;\nstatic int num_exceptions = 0;\nstatic int num_exceptions_max = 1;\n\nstatic bool_t default_deny = FALSE;\n\n/*\n * Parse domainrc.\n */\nvoid a_Domain_parse(FILE *fp)\n{\n   char *line;\n   uint_t lineno = 0;\n\n   _MSG(\"Reading domainrc...\\n\");\n\n   while ((line = dGetline(fp)) != NULL) {\n      ++lineno;\n\n      /* Remove leading and trailing whitespace */\n      dStrstrip(line);\n\n      if (line[0] && line[0] != '#') {\n         const char *delim = \" \\t\";\n         char *tok1 = strtok(line, delim);\n         char *tok2 = strtok(NULL, delim);\n\n         if (strtok(NULL, delim) != NULL) {\n            MSG(\"Domain: Ignoring extraneous text at end of line %u.\\n\",\n                lineno);\n         }\n         if (!tok2) {\n            MSG(\"Domain: Not enough fields in line %u.\\n\", lineno);\n         } else {\n            if (dStrAsciiCasecmp(tok1, \"default\") == 0) {\n               if (dStrAsciiCasecmp(tok2, \"deny\") == 0) {\n                  default_deny = TRUE;\n                  MSG(\"Domain: Default deny.\\n\");\n               } else if (dStrAsciiCasecmp(tok2, \"accept\") == 0) {\n                  default_deny = FALSE;\n                  MSG(\"Domain: Default accept.\\n\");\n               } else {\n                  MSG(\"Domain: Default action \\\"%s\\\" not recognised.\\n\", tok2);\n               }\n            } else {\n               a_List_add(exceptions, num_exceptions, num_exceptions_max);\n               exceptions[num_exceptions].origin = dStrdup(tok1);\n               exceptions[num_exceptions].destination = dStrdup(tok2);\n               num_exceptions++;\n               _MSG(\"Domain: Exception from %s to %s.\\n\", tok1, tok2);\n            }\n         }\n      }\n      dFree(line);\n   }\n}\n\nvoid a_Domain_freeall(void)\n{\n   int i = 0;\n\n   for (i = 0; i < num_exceptions; i++) {\n      dFree(exceptions[i].origin);\n      dFree(exceptions[i].destination);\n   }\n   dFree(exceptions);\n}\n\n/*\n * Wildcard ('*') pattern always matches.\n * \"example.org\" pattern matches \"example.org\".\n * \".example.org\" pattern matches \"example.org\" and \"sub.example.org\".\n */\nstatic bool_t Domain_match(const char *host, const char *pattern) {\n   int cmp = strcmp(pattern, \"*\");\n\n   if (cmp) {\n      if (pattern[0] != '.')\n         cmp = dStrAsciiCasecmp(host, pattern);\n      else {\n         int diff = strlen(host) - strlen(pattern);\n\n         if (diff == -1)\n            cmp = dStrAsciiCasecmp(host, pattern + 1);\n         else if (diff >= 0)\n            cmp = dStrAsciiCasecmp(host + diff, pattern);\n      }\n   }\n   return cmp ? FALSE : TRUE;\n}\n\n/*\n * Is the resource at 'source' permitted to request the resource at 'dest'?\n */\nbool_t a_Domain_permit(const DilloUrl *source, const DilloUrl *dest)\n{\n   int i;\n   bool_t ret;\n   const char *source_host, *dest_host;\n\n   if (default_deny == FALSE && num_exceptions == 0)\n      return TRUE;\n\n   source_host = URL_HOST(source);\n   dest_host = URL_HOST(dest);\n\n   if (dest_host[0] == '\\0') {\n      ret = source_host[0] == '\\0' ||\n            !dStrAsciiCasecmp(URL_SCHEME(dest), \"data\");\n      if (ret == FALSE)\n         MSG(\"Domain: DENIED %s -> %s.\\n\", source_host, URL_STR(dest));\n      return ret;\n   }\n\n   if (a_Url_same_organization(source, dest))\n      return TRUE;\n\n   ret = default_deny ? FALSE : TRUE;\n\n   for (i = 0; i < num_exceptions; i++) {\n      if (Domain_match(source_host, exceptions[i].origin) &&\n          Domain_match(dest_host, exceptions[i].destination)) {\n         ret = default_deny;\n         _MSG(\"Domain: Matched rule from %s to %s.\\n\", exceptions[i].origin,\n              exceptions[i].destination);\n         break;\n      }\n   }\n\n   if (ret == FALSE) {\n      const char *src = source_host[0] ? source_host : URL_STR(source);\n\n      MSG(\"Domain: DENIED %s -> %s.\\n\", src, dest_host);\n   }\n   return ret;\n}\n"
  },
  {
    "path": "src/domain.h",
    "content": "#ifndef __DOMAIN_H__\n#define __DOMAIN_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stdio.h>\n#include \"url.h\"\n\nvoid a_Domain_parse(FILE *fp);\nvoid a_Domain_freeall(void);\nbool_t a_Domain_permit(const DilloUrl *source, const DilloUrl *dest);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "src/domainrc",
    "content": "# domainrc - Dillo cross-domain request rules file.\n#\n# Here you can tell Dillo what to do when one site wants to retrieve resources\n# (e.g., images, style sheets, redirection) from a different site.\n#\n# Lines that begin with a '#' are comments.\n\n# Default rule can be \"accept\" or \"deny\".\n\ndefault accept\n\n\n# Now we list exceptions to the default. The format is:\n#\n# source destination\n#\n# There are three ways that you can specify a source or destination domain:\n#\n# 1. *                - wildcard will match any domain\n# 2. example.com      - match the specific host example.com\n# 3. .example.com     - match example.com and any of its subdomains\n\n# Let's block some of the most notorious ad sites and trackers.\n\n* .2o7.net\n* .admt.com\n* .adnxs.com\n* .atdmt.com\n* .collective-media.net\n* .crwdcntrl.com\n* .doubleclick.net\n* .effectivemeasure.net\n* .googleadservices.com\n* .imrworldwide.com\n* .quantserve.com\n* .revsci.net\n* .scorecardresearch.com\n* .webtrendslive.com\n* .yieldmanager.com\n"
  },
  {
    "path": "src/dpiapi.c",
    "content": "/*\n * File: dpiapi.c\n *\n * Copyright (C) 2004-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/* Support for dpi/dpip from Dillo's side */\n\n#include \"msg.h\"\n#include \"bw.h\"\n#include \"capi.h\"\n#include \"dpiapi.h\"  /* for prototypes */\n#include \"dialog.hh\"\n#include \"../dpip/dpip.h\"\n\n\n//----------------------------------------------------------------------------\n// Dialog interface\n//\n\n/* This variable can be eliminated as a parameter with a cleaner API. */\nstatic char *dialog_server = NULL;\n\n\n/*\n * Generic callback function for dpip dialogs.\n */\nstatic void Dpiapi_dialog_answer_cb(BrowserWindow *bw, int answer)\n{\n   char *cmd, numstr[16];\n\n   /* make dpip tag with the answer */\n   snprintf(numstr, 16, \"%d\", answer);\n   cmd = a_Dpip_build_cmd(\"cmd=%s to_cmd=%s msg=%s\",\n                          \"answer\", \"dialog\", numstr);\n\n   /* Send answer */\n   a_Capi_dpi_send_cmd(NULL, bw, cmd, dialog_server, 0);\n   dFree(cmd);\n}\n\n/*\n * Process a dpip \"dialog\" command from any dpi.\n */\nvoid a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag)\n{\n   char *title, *msg, *alt1, *alt2, *alt3, *alt4, *alt5;\n   size_t dpip_tag_len;\n   int ret;\n\n   _MSG(\"a_Dpiapi_dialog:\\n\");\n   _MSG(\"  dpip_tag: %s\\n\", dpip_tag);\n\n   /* set the module scoped variable */\n   dialog_server = server;\n\n   /* other options can be parsed the same way */\n   dpip_tag_len = strlen(dpip_tag);\n   title = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, \"title\");\n   msg = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, \"msg\");\n   alt1 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, \"alt1\");\n   alt2 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, \"alt2\");\n   alt3 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, \"alt3\");\n   alt4 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, \"alt4\");\n   alt5 = a_Dpip_get_attr_l(dpip_tag, dpip_tag_len, \"alt5\");\n\n   ret = a_Dialog_choice(title, msg, alt1, alt2, alt3, alt4, alt5, NULL);\n   /* As choice is modal, call the callback function directly. */\n   Dpiapi_dialog_answer_cb(bw, ret);\n\n   dFree(alt1); dFree(alt2); dFree(alt3); dFree(alt4); dFree(alt5);\n   dFree(title); dFree(msg);\n}\n\n"
  },
  {
    "path": "src/dpiapi.h",
    "content": "\nvoid a_Dpiapi_dialog(BrowserWindow *bw, char *server, char *dpip_tag);\n\n"
  },
  {
    "path": "src/dpng.h",
    "content": "#ifndef __PNG_H__\n#define __PNG_H__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#include \"url.h\"\n#include \"image.hh\"\n\n\nvoid *a_Png_new(DilloImage *Image, DilloUrl *url, int version);\nvoid a_Png_callback(int Op, CacheClient_t *Client);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* !__PNG_H__ */\n"
  },
  {
    "path": "src/findbar.cc",
    "content": "/*\n * File: findbar.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n#include \"findbar.hh\"\n\n#include \"msg.h\"\n#include \"pixmaps.h\"\n#include \"uicmd.hh\"\n#include \"bw.h\"\n\n/*\n * Local sub class\n * (Used to handle escape in the findbar, may also avoid some shortcuts).\n */\nclass MyInput : public Fl_Input {\npublic:\n   MyInput (int x, int y, int w, int h, const char* l=0) :\n      Fl_Input(x,y,w,h,l) {};\n   int handle(int e);\n};\n\nint MyInput::handle(int e)\n{\n   _MSG(\"findbar MyInput::handle()\\n\");\n   int ret = 1, k = Fl::event_key();\n   unsigned modifier = Fl::event_state() & (FL_SHIFT| FL_CTRL| FL_ALT|FL_META);\n\n   if (e == FL_KEYBOARD) {\n      if (k == FL_Page_Down || k == FL_Page_Up || k == FL_Up || k == FL_Down) {\n         // Let them through for key commands and viewport motion.\n         return 0;\n      }\n      if (modifier == FL_SHIFT) {\n         if (k == FL_Left || k == FL_Right) {\n            // Let these keys get to the UI\n            return 0;\n         }\n      } else if (modifier == FL_CTRL) {\n         if (k == 'A') {\n            position(size(), 0);\n            return 1;\n         } if (k == 'a' || k == 'e') {\n            position(k == 'a' ? 0 : size());\n            return 1;\n         } else if (k == 'k') {\n            cut(position(), size());\n            return 1;\n         } else if (k == 'd') {\n            cut(position(), position()+1);\n            return 1;\n         } else if (k == 'h' || k == 'i' || k == 'j' || k == 'l' || k == 'm') {\n            // Fl_Input wants to use ^H as backspace, and also \"insert a few\n            // selected control characters literally\", but this gets in the way\n            // of key commands.\n            return 0;\n         }\n      } else if (k == FL_Escape && modifier == 0) {\n         // Avoid clearing the text with Esc, just hide the findbar.\n         return 0;\n      }\n   }\n\n   if (ret)\n      ret = Fl_Input::handle(e);\n   return ret;\n}\n\n/*\n * Find next occurrence of input key\n */\nvoid Findbar::search_cb(Fl_Widget *, void *vfb)\n{\n   Findbar *fb = (Findbar *)vfb;\n   const char *key = fb->i->value();\n   bool case_sens = fb->check_btn->value();\n\n   if (key[0] != '\\0')\n      a_UIcmd_findtext_search(a_UIcmd_get_bw_by_widget(fb),\n                              key, case_sens, false);\n}\n\n/*\n * Find previous occurrence of input key\n */\nvoid Findbar::searchBackwards_cb(Fl_Widget *, void *vfb)\n{\n   Findbar *fb = (Findbar *)vfb;\n   const char *key = fb->i->value();\n   bool case_sens = fb->check_btn->value();\n\n   if (key[0] != '\\0') {\n      a_UIcmd_findtext_search(a_UIcmd_get_bw_by_widget(fb),\n                              key, case_sens, true);\n   }\n}\n\n/*\n * Hide the search bar\n */\nvoid Findbar::hide_cb(Fl_Widget *, void *vfb)\n{\n   a_UIcmd_findbar_toggle(a_UIcmd_get_bw_by_widget(vfb), 0);\n}\n\n/*\n * Construct text search bar\n */\nFindbar::Findbar(int width, int height) :\n   Fl_Group(0, 0, width, height)\n{\n   int button_width = 70;\n   int gap = 2;\n   int border = 2;\n   int input_width = width - (2 * border + 4 * (button_width + gap));\n   int x = 0;\n\n   Fl_Group::current(0);\n\n   height -= 2 * border;\n\n   box(FL_THIN_UP_BOX);\n\n    hide_btn = new CustButton(x, border, 16, height, 0);\n    hideImg = new Fl_Pixmap(new_s_xpm);\n    hide_btn->image(hideImg);\n    x += 16 + gap;\n    hide_btn->callback(hide_cb, this);\n    hide_btn->clear_visible_focus();\n    hide_btn->box(FL_THIN_UP_BOX);\n    hide_btn->set_tooltip(\"Hide\");\n   add(hide_btn);\n\n    i = new MyInput(x, border, input_width, height);\n    x += input_width + gap;\n    resizable(i);\n    i->when(FL_WHEN_NEVER);\n   add(i);\n\n    next_btn = new CustButton(x, border, button_width, height, \"Next\");\n    x += button_width + gap;\n    next_btn->shortcut(FL_Enter);\n    next_btn->callback(search_cb, this);\n    next_btn->clear_visible_focus();\n    next_btn->box(FL_THIN_UP_BOX);\n    next_btn->set_tooltip(\"Find next occurrence of the search phrase\\n\"\n                          \"shortcut: Enter\");\n   add(next_btn);\n\n    prev_btn= new CustButton(x, border, button_width, height, \"Previous\");\n    x += button_width + gap;\n    prev_btn->shortcut(FL_SHIFT+FL_Enter);\n    prev_btn->callback(searchBackwards_cb, this);\n    prev_btn->clear_visible_focus();\n    prev_btn->box(FL_THIN_UP_BOX);\n    prev_btn->set_tooltip(\"Find previous occurrence of the search phrase\\n\"\n                          \"shortcut: Shift+Enter\");\n   add(prev_btn);\n\n    check_btn = new Fl_Check_Button(x, border, 2*button_width, height,\n                              \"Case-sensitive\");\n    x += 2 * button_width + gap;\n    check_btn->clear_visible_focus();\n   add(check_btn);\n\n}\n\nFindbar::~Findbar()\n{\n   delete hideImg;\n}\n\n/*\n * Handle events. Used to catch FL_Escape events.\n */\nint Findbar::handle(int event)\n{\n   int k = Fl::event_key();\n   unsigned modifier = Fl::event_state() & (FL_SHIFT| FL_CTRL| FL_ALT|FL_META);\n\n   if (event == FL_KEYBOARD && modifier == 0 && k == FL_Escape) {\n      /* let the UI handle it */\n      return 0;\n   }\n\n   return Fl_Group::handle(event);\n}\n\n/*\n * Show the findbar and focus the input field\n */\nvoid Findbar::show()\n{\n   BrowserWindow *bw = a_UIcmd_get_bw_by_widget(this);\n   dReturn_if (bw == NULL);\n\n   // It takes more than just calling show() to do the trick\n   Fl_Group::show();\n\n   /* select text even if already focused */\n   i->take_focus();\n   i->position(i->size(), 0);\n}\n\n"
  },
  {
    "path": "src/findbar.hh",
    "content": "#ifndef __FINDBAR_HH__\n#define __FINDBAR_HH__\n\n#include <FL/Fl_Pixmap.H>\n#include <FL/Fl_Widget.H>\n#include <FL/Fl_Button.H>\n#include <FL/Fl_Input.H>\n#include <FL/Fl_Group.H>\n#include <FL/Fl_Check_Button.H>\n\n#include \"tipwin.hh\"\n\n/*\n * Searchbar to find text in page.\n */\nclass Findbar : public Fl_Group {\n   CustButton *hide_btn, *next_btn, *prev_btn;\n   Fl_Check_Button *check_btn;\n   Fl_Pixmap *hideImg;\n   Fl_Input *i;\n\n   static void search_cb (Fl_Widget *, void *);\n   static void searchBackwards_cb (Fl_Widget *, void *);\n   static void hide_cb (Fl_Widget *, void *);\n\npublic:\n   Findbar(int width, int height);\n   ~Findbar();\n   int handle(int event);\n   void show();\n};\n\n#endif // __FINDBAR_HH__\n"
  },
  {
    "path": "src/form.cc",
    "content": "/*\n * File: form.cc\n *\n * Copyright 2008 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include \"form.hh\"\n#include \"html_common.hh\"\n\n#include <errno.h>\n#include <iconv.h>\n\n#include \"lout/misc.hh\"\n#include \"dw/core.hh\"\n#include \"dw/textblock.hh\"\n\n#include \"misc.h\"\n#include \"msg.h\"\n#include \"prefs.h\"\n#include \"uicmd.hh\"\n#include \"dialog.hh\"\n\nusing namespace lout;\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::core::ui;\n\n/*\n * Forward declarations\n */\n\nclass DilloHtmlReceiver;\nclass DilloHtmlSelect;\n\nstatic Embed *Html_input_image(DilloHtml *html, const char *tag, int tagsize);\n\nstatic void Html_option_finish(DilloHtml *html);\n\n/*\n * Typedefs\n */\n\ntypedef enum {\n   DILLO_HTML_INPUT_UNKNOWN,\n   DILLO_HTML_INPUT_TEXT,\n   DILLO_HTML_INPUT_PASSWORD,\n   DILLO_HTML_INPUT_CHECKBOX,\n   DILLO_HTML_INPUT_RADIO,\n   DILLO_HTML_INPUT_IMAGE,\n   DILLO_HTML_INPUT_FILE,\n   DILLO_HTML_INPUT_BUTTON,\n   DILLO_HTML_INPUT_HIDDEN,\n   DILLO_HTML_INPUT_SUBMIT,\n   DILLO_HTML_INPUT_RESET,\n   DILLO_HTML_INPUT_BUTTON_SUBMIT,\n   DILLO_HTML_INPUT_BUTTON_RESET,\n   DILLO_HTML_INPUT_SELECT,\n   DILLO_HTML_INPUT_SEL_LIST,\n   DILLO_HTML_INPUT_TEXTAREA,\n   DILLO_HTML_INPUT_INDEX\n} DilloHtmlInputType;\n\n/*\n * Class declarations\n */\n\nclass DilloHtmlForm {\n   friend class DilloHtmlReceiver;\n   friend class DilloHtmlInput;\n\n   DilloHtml *html;\n   bool showing_hiddens;\n   bool enabled;\n   void eventHandler(Resource *resource, EventButton *event);\n   DilloUrl *buildQueryUrl(DilloHtmlInput *active_input);\n   Dstr *buildQueryData(DilloHtmlInput *active_submit);\n   char *makeMultipartBoundary(iconv_t char_encoder,\n                               DilloHtmlInput *active_submit);\n   Dstr *encodeText(iconv_t char_encoder, Dstr **input);\n   void strUrlencodeAppend(Dstr *dstr, const char *str);\n   void inputUrlencodeAppend(Dstr *data, const char *name, const char *value);\n   void inputMultipartAppend(Dstr *data, const char *boundary,\n                             const char *name, const char *value);\n   void filesInputMultipartAppend(Dstr* data, const char *boundary,\n                                  const char *name, Dstr *file,\n                                  const char *filename);\n   void imageInputUrlencodeAppend(Dstr *data, Dstr *name, Dstr *x, Dstr *y);\n   void imageInputMultipartAppend(Dstr *data, const char *boundary, Dstr *name,\n                                  Dstr *x, Dstr *y);\n\npublic:  //BUG: for now everything is public\n   DilloHtmlMethod method;\n   DilloUrl *action;\n   DilloHtmlEnc content_type;\n   char *submit_charset;\n\n   lout::misc::SimpleVector<DilloHtmlInput*> *inputs;\n\n   int num_entry_fields;\n\n   DilloHtmlReceiver *form_receiver;\n\npublic:\n   DilloHtmlForm (DilloHtml *html,\n                  DilloHtmlMethod method, const DilloUrl *action,\n                  DilloHtmlEnc content_type, const char *charset,\n                  bool enabled);\n   ~DilloHtmlForm ();\n   DilloHtmlInput *getInput (Resource *resource);\n   DilloHtmlInput *getRadioInput (const char *name);\n   void submit(DilloHtmlInput *active_input, EventButton *event);\n   void reset ();\n   void display_hiddens(bool display);\n   void addInput(DilloHtmlInput *input, DilloHtmlInputType type);\n   void setEnabled(bool enabled);\n};\n\nclass DilloHtmlReceiver:\n   public Resource::ActivateReceiver,\n   public Resource::ClickedReceiver\n{\n   friend class DilloHtmlForm;\n   DilloHtmlForm* form;\n   DilloHtmlReceiver (DilloHtmlForm* form2) { form = form2; }\n   ~DilloHtmlReceiver () { }\n   void activate (Resource *resource);\n   void enter (Resource *resource);\n   void leave (Resource *resource);\n   void clicked (Resource *resource, EventButton *event);\n};\n\nclass DilloHtmlInput {\n\n   // DilloHtmlForm::addInput() calls connectTo()\n   friend class DilloHtmlForm;\n\npublic:  //BUG: for now everything is public\n   DilloHtmlInputType type;\n   Embed *embed; /* May be NULL (think: hidden input) */\n   char *name;\n   char *init_str;    /* note: some overloading - for buttons, init_str\n                         is simply the value of the button; for text\n                         entries, it is the initial value */\n   DilloHtmlSelect *select;\n   bool init_val;     /* only meaningful for buttons */\n   Dstr *file_data;   /* only meaningful for file inputs.\n                         TODO: may become a list... */\n\nprivate:\n   void connectTo(DilloHtmlReceiver *form_receiver);\n   void activate(DilloHtmlForm *form, int num_entry_fields,EventButton *event);\n   void readFile(BrowserWindow *bw);\n\npublic:\n   DilloHtmlInput (DilloHtmlInputType type, Embed *embed,\n                   const char *name, const char *init_str, bool init_val);\n   ~DilloHtmlInput ();\n   void appendValuesTo(Dlist *values, bool is_active_submit);\n   void reset();\n   void setEnabled(bool enabled) {if (embed) embed->setEnabled(enabled); };\n};\n\nclass DilloHtmlOptbase\n{\npublic:\n   virtual ~DilloHtmlOptbase () {};\n   virtual bool isSelected() {return false;}\n   virtual bool select() {return false;}\n   virtual const char *getValue() {return NULL;}\n   virtual void setContent(const char *str, int len)\n      {MSG_ERR(\"Form: Optbase setContent()\\n\");}\n   virtual void addSelf(SelectionResource *res) = 0;\n};\n\nclass DilloHtmlOptgroup : public DilloHtmlOptbase {\nprivate:\n   char *label;\n   bool enabled;\npublic:\n   DilloHtmlOptgroup (char *label, bool enabled);\n   virtual ~DilloHtmlOptgroup ();\n   void addSelf (SelectionResource *res)\n      {res->pushGroup(label, enabled);}\n};\n\nclass DilloHtmlOptgroupClose : public DilloHtmlOptbase {\npublic:\n   virtual ~DilloHtmlOptgroupClose () {};\n   void addSelf (SelectionResource *res)\n      {res->popGroup();}\n};\n\nclass DilloHtmlOption : public DilloHtmlOptbase {\n   friend class DilloHtmlSelect;\npublic:\n   char *value, *label, *content;\n   bool selected, enabled;\n   DilloHtmlOption (char *value, char *label, bool selected, bool enabled);\n   virtual ~DilloHtmlOption ();\n   bool isSelected() {return selected;}\n   bool select() {return (selected = true);}\n   const char *getValue() {return value ? value : content;}\n   void setContent(const char *str, int len) {content = dStrndup(str, len);}\n   void addSelf (SelectionResource *res)\n      {res->addItem(label ? label : content, enabled, selected);}\n};\n\nclass DilloHtmlSelect {\n   friend class DilloHtmlInput;\nprivate:\n   lout::misc::SimpleVector<DilloHtmlOptbase *> *opts;\n   DilloHtmlSelect ();\n   ~DilloHtmlSelect ();\npublic:\n   DilloHtmlOptbase *getCurrentOpt ();\n   void addOpt (DilloHtmlOptbase *opt);\n   void ensureSelection ();\n   void addOptsTo (SelectionResource *res);\n   void reset (SelectionResource *res);\n   void appendValuesTo (Dlist *values, SelectionResource *res);\n};\n\n/*\n * Form API\n */\n\nDilloHtmlForm *a_Html_form_new (DilloHtml *html, DilloHtmlMethod method,\n                                const DilloUrl *action,\n                                DilloHtmlEnc content_type, const char *charset,\n                                bool enabled)\n{\n   return new DilloHtmlForm (html, method, action, content_type, charset,\n                             enabled);\n}\n\nvoid a_Html_form_delete (DilloHtmlForm *form)\n{\n   delete form;\n}\n\nvoid a_Html_input_delete (DilloHtmlInput *input)\n{\n   delete input;\n}\n\nvoid a_Html_form_submit2(void *vform)\n{\n   ((DilloHtmlForm *)vform)->submit(NULL, NULL);\n}\n\nvoid a_Html_form_reset2(void *vform)\n{\n   ((DilloHtmlForm *)vform)->reset();\n}\n\nvoid a_Html_form_display_hiddens2(void *vform, bool display)\n{\n   ((DilloHtmlForm *)vform)->display_hiddens(display);\n}\n\n/*\n * Form parsing functions\n */\n\n/*\n * Add an HTML control\n */\nstatic void Html_add_input(DilloHtml *html, DilloHtmlInputType type,\n                           Embed *embed, const char *name,\n                           const char *init_str, bool init_val)\n{\n   _MSG(\"name=[%s] init_str=[%s] init_val=[%d]\\n\", name, init_str, init_val);\n   DilloHtmlInput *input = new DilloHtmlInput(type, embed, name, init_str,\n                                              init_val);\n   if (html->InFlags & IN_FORM) {\n      html->getCurrentForm()->addInput(input, type);\n   } else {\n      int ni = html->inputs_outside_form->size();\n      html->inputs_outside_form->increase();\n      html->inputs_outside_form->set(ni, input);\n\n      if (html->bw->NumPendingStyleSheets > 0) {\n         input->setEnabled(false);\n      }\n   }\n}\n\n/*\n * Find radio input by name\n */\nstatic DilloHtmlInput *Html_get_radio_input(DilloHtml *html, const char *name)\n{\n   if (name) {\n      lout::misc::SimpleVector<DilloHtmlInput*>* inputs;\n\n      if (html->InFlags & IN_FORM)\n         inputs = html->getCurrentForm()->inputs;\n      else\n         inputs = html->inputs_outside_form;\n\n      for (int idx = 0; idx < inputs->size(); idx++) {\n         DilloHtmlInput *input = inputs->get(idx);\n         if (input->type == DILLO_HTML_INPUT_RADIO &&\n             input->name && !dStrAsciiCasecmp(input->name, name))\n            return input;\n      }\n   }\n   return NULL;\n}\n\n/*\n * Get the current input if available.\n */\nstatic DilloHtmlInput *Html_get_current_input(DilloHtml *html)\n{\n   lout::misc::SimpleVector<DilloHtmlInput*>* inputs;\n\n   if (html->InFlags & IN_FORM)\n      inputs = html->getCurrentForm()->inputs;\n   else\n      inputs = html->inputs_outside_form;\n\n   return (inputs && inputs->size() > 0) ?\n            inputs->get (inputs->size() - 1) : NULL;\n}\n\n/*\n * Handle <FORM> tag\n */\nvoid Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloUrl *action;\n   DilloHtmlMethod method;\n   DilloHtmlEnc content_type;\n   char *charset, *first;\n   const char *attrbuf;\n\n   HT2TB(html)->addParbreak (9, html->wordStyle ());\n\n   if (html->InFlags & IN_FORM) {\n      BUG_MSG(\"Nested <form>.\");\n      return;\n   }\n   html->InFlags |= IN_FORM;\n   html->InFlags &= ~IN_SELECT;\n   html->InFlags &= ~IN_OPTION;\n   html->InFlags &= ~IN_TEXTAREA;\n\n   method = DILLO_HTML_METHOD_GET;\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"method\"))) {\n      if (!dStrAsciiCasecmp(attrbuf, \"post\")) {\n         method = DILLO_HTML_METHOD_POST;\n      } else if (dStrAsciiCasecmp(attrbuf, \"get\")) {\n         BUG_MSG(\"<form> submission method unknown: '%s'.\", attrbuf);\n      }\n   }\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"action\")))\n      action = a_Html_url_new(html, attrbuf, NULL, 0);\n   else {\n      if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f)\n         BUG_MSG(\"<form> requires action attribute.\");\n      action = a_Url_dup(html->base_url);\n   }\n   content_type = DILLO_HTML_ENC_URLENCODED;\n   if ((method == DILLO_HTML_METHOD_POST) &&\n       ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"enctype\")))) {\n      if (!dStrAsciiCasecmp(attrbuf, \"multipart/form-data\"))\n         content_type = DILLO_HTML_ENC_MULTIPART;\n   }\n   charset = NULL;\n   first = NULL;\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"accept-charset\"))) {\n      /* a list of acceptable charsets, separated by commas or spaces */\n      char *ptr = first = dStrdup(attrbuf);\n      while (ptr && !charset) {\n         char *curr = dStrsep(&ptr, \" ,\");\n         if (!dStrAsciiCasecmp(curr, \"utf-8\")) {\n            charset = curr;\n         } else if (!dStrAsciiCasecmp(curr, \"UNKNOWN\")) {\n            /* defined to be whatever encoding the document is in */\n            charset = html->charset;\n         }\n      }\n      if (!charset)\n         charset = first;\n   }\n   if (!charset)\n      charset = html->charset;\n   html->formNew(method, action, content_type, charset);\n   dFree(first);\n   a_Url_free(action);\n}\n\nvoid Html_tag_close_form(DilloHtml *html)\n{\n   html->InFlags &= ~IN_FORM;\n   html->InFlags &= ~IN_SELECT;\n   html->InFlags &= ~IN_OPTION;\n   html->InFlags &= ~IN_TEXTAREA;\n}\n\n/*\n * get size, restrict it to reasonable value\n */\nstatic int Html_input_get_size(DilloHtml *html, const char *attrbuf)\n{\n   const int MAX_SIZE = 1024;\n   int size = 20;\n\n   if (attrbuf) {\n      size = strtol(attrbuf, NULL, 10);\n      if (size < 1 || size > MAX_SIZE) {\n         int badSize = size;\n         size = (size < 1 ? 20 : MAX_SIZE);\n         BUG_MSG(\"<input> size=%d, using size=%d instead.\", badSize, size);\n      }\n   }\n   return size;\n}\n\n/*\n * Add a new input to current form\n */\nvoid Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloHtmlInputType inp_type;\n   Resource *resource = NULL;\n   Embed *embed = NULL;\n   char *value, *name, *type, *init_str, *placeholder = NULL;\n   const char *attrbuf, *label;\n   bool init_val = false;\n   ResourceFactory *factory;\n\n   if (html->InFlags & IN_SELECT) {\n      BUG_MSG(\"<input> inside <select>.\");\n      return;\n   }\n   if (html->InFlags & IN_BUTTON) {\n      BUG_MSG(\"<input> inside <button>.\");\n      return;\n   }\n\n   factory = HT2LT(html)->getResourceFactory();\n\n   /* Get 'value', 'name' and 'type' */\n   value = a_Html_get_attr_wdef(html, tag, tagsize, \"value\", NULL);\n   name = a_Html_get_attr_wdef(html, tag, tagsize, \"name\", NULL);\n   type = a_Html_get_attr_wdef(html, tag, tagsize, \"type\", \"\");\n\n   if (a_Html_get_attr(html, tag, tagsize, \"hidden\")) {\n\t   // TODO: set type to 'hidden' directly, since I don't think attribute is used\n      type = a_Html_get_attr_wdef(html, tag, tagsize, \"type\", \"hidden\");\n   }\n\n   init_str = NULL;\n   inp_type = DILLO_HTML_INPUT_UNKNOWN;\n   if (!dStrAsciiCasecmp(type, \"password\")) {\n      inp_type = DILLO_HTML_INPUT_PASSWORD;\n      placeholder = a_Html_get_attr_wdef(html, tag,tagsize,\"placeholder\",NULL);\n      attrbuf = a_Html_get_attr(html, tag, tagsize, \"size\");\n      int size = Html_input_get_size(html, attrbuf);\n      resource = factory->createEntryResource (size, true, NULL, placeholder);\n      init_str = value;\n   } else if (!dStrAsciiCasecmp(type, \"checkbox\")) {\n      inp_type = DILLO_HTML_INPUT_CHECKBOX;\n      resource = factory->createCheckButtonResource(false);\n      init_val = (a_Html_get_attr(html, tag, tagsize, \"checked\") != NULL);\n      init_str = (value) ? value : dStrdup(\"on\");\n   } else if (!dStrAsciiCasecmp(type, \"radio\")) {\n      inp_type = DILLO_HTML_INPUT_RADIO;\n      RadioButtonResource *rb_r = NULL;\n      DilloHtmlInput *input = Html_get_radio_input(html, name);\n      if (input)\n         rb_r = (RadioButtonResource*) input->embed->getResource();\n      resource = factory->createRadioButtonResource(rb_r, false);\n      init_val = (a_Html_get_attr(html, tag, tagsize, \"checked\") != NULL);\n      init_str = value;\n   } else if (!dStrAsciiCasecmp(type, \"hidden\")) {\n      inp_type = DILLO_HTML_INPUT_HIDDEN;\n      init_str = value;\n      int size = Html_input_get_size(html, NULL);\n      resource = factory->createEntryResource(size, false, name, NULL);\n   } else if (!dStrAsciiCasecmp(type, \"submit\")) {\n      inp_type = DILLO_HTML_INPUT_SUBMIT;\n      init_str = (value) ? value : dStrdup(\"submit\");\n      resource = factory->createLabelButtonResource(init_str);\n   } else if (!dStrAsciiCasecmp(type, \"reset\")) {\n      inp_type = DILLO_HTML_INPUT_RESET;\n      init_str = (value) ? value : dStrdup(\"Reset\");\n      resource = factory->createLabelButtonResource(init_str);\n   } else if (!dStrAsciiCasecmp(type, \"image\")) {\n      if (URL_FLAGS(html->base_url) & URL_SpamSafe) {\n         /* Don't request the image; make a text submit button instead */\n         inp_type = DILLO_HTML_INPUT_SUBMIT;\n         attrbuf = a_Html_get_attr(html, tag, tagsize, \"alt\");\n         label = attrbuf ? attrbuf : value ? value : name ? name : \"Submit\";\n         init_str = dStrdup(label);\n         resource = factory->createLabelButtonResource(init_str);\n      } else {\n         inp_type = DILLO_HTML_INPUT_IMAGE;\n         /* use a dw_image widget */\n         embed = Html_input_image(html, tag, tagsize);\n         init_str = value;\n      }\n   } else if (!dStrAsciiCasecmp(type, \"file\")) {\n      bool valid = true;\n      if (html->InFlags & IN_FORM) {\n         DilloHtmlForm *form = html->getCurrentForm();\n         if (form->method != DILLO_HTML_METHOD_POST) {\n            valid = false;\n            BUG_MSG(\"<form> with file input MUST use HTTP POST method.\");\n            MSG(\"File input ignored in form not using HTTP POST method\\n\");\n         } else if (form->content_type != DILLO_HTML_ENC_MULTIPART) {\n            valid = false;\n            BUG_MSG(\"<form> with file input MUST use multipart/form-data\"\n                    \" encoding.\");\n            MSG(\"File input ignored in form not using multipart/form-data\"\n                \" encoding\\n\");\n         }\n      }\n      if (valid) {\n         inp_type = DILLO_HTML_INPUT_FILE;\n         init_str = dStrdup(\"File selector\");\n         resource = factory->createLabelButtonResource(init_str);\n      }\n   } else if (!dStrAsciiCasecmp(type, \"button\")) {\n      inp_type = DILLO_HTML_INPUT_BUTTON;\n      if (value) {\n         init_str = value;\n         resource = factory->createLabelButtonResource(init_str);\n      }\n   } else {\n      /* Text input, which also is the default */\n      inp_type = DILLO_HTML_INPUT_TEXT;\n      placeholder = a_Html_get_attr_wdef(html, tag,tagsize,\"placeholder\",NULL);\n      attrbuf = a_Html_get_attr(html, tag, tagsize, \"size\");\n      int size = Html_input_get_size(html, attrbuf);\n      resource = factory->createEntryResource(size, false, NULL, placeholder);\n      init_str = value;\n   }\n   if (resource)\n      embed = new Embed (resource);\n\n   if (inp_type != DILLO_HTML_INPUT_UNKNOWN) {\n      Html_add_input(html, inp_type, embed, name,\n                     (init_str) ? init_str : \"\", init_val);\n   }\n\n   if (embed != NULL && inp_type != DILLO_HTML_INPUT_IMAGE &&\n       inp_type != DILLO_HTML_INPUT_UNKNOWN) {\n      if (inp_type == DILLO_HTML_INPUT_HIDDEN) {\n         /* TODO Perhaps do this with access to current form setting */\n         embed->setDisplayed(false);\n      }\n      if (inp_type == DILLO_HTML_INPUT_TEXT ||\n          inp_type == DILLO_HTML_INPUT_PASSWORD) {\n         if (a_Html_get_attr(html, tag, tagsize, \"readonly\"))\n            ((EntryResource *) resource)->setEditable(false);\n\n         /* Maximum length of the text in the entry */\n         if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"maxlength\"))) {\n            int maxlen = strtol(attrbuf, NULL, 10);\n            ((EntryResource *) resource)->setMaxLength(maxlen);\n         }\n      }\n      if (prefs.show_tooltip &&\n          (attrbuf = a_Html_get_attr(html, tag, tagsize, \"title\"))) {\n\n         html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,\n                                           attrbuf);\n      }\n      HT2TB(html)->addWidget (embed, html->backgroundStyle());\n   }\n   dFree(type);\n   dFree(name);\n   if (init_str != value)\n      dFree(init_str);\n   dFree(placeholder);\n   dFree(value);\n}\n\n/*\n * The ISINDEX tag is just a deprecated form of <INPUT type=text> with\n * implied FORM, afaics.\n */\nvoid Html_tag_open_isindex(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloUrl *action;\n   Embed *embed;\n   const char *attrbuf;\n\n   if (html->InFlags & IN_FORM) {\n      MSG(\"<isindex> inside <form> not handled.\\n\");\n      return;\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"action\")))\n      action = a_Html_url_new(html, attrbuf, NULL, 0);\n   else\n      action = a_Url_dup(html->base_url);\n\n   html->formNew(DILLO_HTML_METHOD_GET, action, DILLO_HTML_ENC_URLENCODED,\n                 html->charset);\n   html->InFlags |= IN_FORM;\n\n   HT2TB(html)->addParbreak (9, html->wordStyle ());\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"prompt\")))\n      HT2TB(html)->addText(attrbuf, html->wordStyle ());\n\n   ResourceFactory *factory = HT2LT(html)->getResourceFactory();\n   EntryResource *entryResource = factory->createEntryResource (20, false,\n                                                                NULL, NULL);\n   embed = new Embed (entryResource);\n   Html_add_input(html, DILLO_HTML_INPUT_INDEX, embed, NULL, NULL, FALSE);\n\n   HT2TB(html)->addWidget (embed, html->backgroundStyle ());\n\n   a_Url_free(action);\n   html->InFlags &= ~IN_FORM;\n}\n\nvoid Html_tag_open_textarea(DilloHtml *html, const char *tag, int tagsize)\n{\n   assert((html->InFlags & (IN_BUTTON | IN_SELECT | IN_TEXTAREA)) == 0);\n\n   html->InFlags |= IN_TEXTAREA;\n}\n\n/*\n * The textarea tag\n */\nvoid Html_tag_content_textarea(DilloHtml *html, const char *tag, int tagsize)\n{\n   const int MAX_COLS=1024, MAX_ROWS=10000;\n\n   char *name;\n   const char *attrbuf;\n   int cols, rows;\n\n   a_Html_stash_init(html);\n   S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"cols\"))) {\n      cols = strtol(attrbuf, NULL, 10);\n   } else {\n      if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f)\n         BUG_MSG(\"<textarea> requires cols attribute.\");\n      cols = 20;\n   }\n   if (cols < 1 || cols > MAX_COLS) {\n      int badCols = cols;\n      cols = (cols < 1 ? 20 : MAX_COLS);\n      BUG_MSG(\"<textarea> cols=%d, using cols=%d instead.\", badCols, cols);\n   }\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"rows\"))) {\n      rows = strtol(attrbuf, NULL, 10);\n   } else {\n      if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f)\n         BUG_MSG(\"<textarea> requires rows attribute.\");\n      rows = 3;\n   }\n   if (rows < 1 || rows > MAX_ROWS) {\n      int badRows = rows;\n      rows = (rows < 1 ? 2 : MAX_ROWS);\n      BUG_MSG(\"<textarea> rows=%d, using rows=%d instead.\", badRows, rows);\n   }\n   name = NULL;\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"name\")))\n      name = dStrdup(attrbuf);\n\n   attrbuf = a_Html_get_attr(html, tag, tagsize, \"placeholder\");\n\n   ResourceFactory *factory = HT2LT(html)->getResourceFactory();\n   MultiLineTextResource *textres =\n      factory->createMultiLineTextResource (cols, rows, attrbuf);\n\n   Embed *embed = new Embed(textres);\n   /* Readonly or not? */\n   if (a_Html_get_attr(html, tag, tagsize, \"readonly\"))\n      textres->setEditable(false);\n   Html_add_input(html, DILLO_HTML_INPUT_TEXTAREA, embed, name, NULL, false);\n\n   HT2TB(html)->addWidget (embed, html->backgroundStyle ());\n   dFree(name);\n}\n\n/*\n * Close  textarea\n * (TEXTAREA is parsed in VERBATIM mode, and entities are handled here)\n */\nvoid Html_tag_close_textarea(DilloHtml *html)\n{\n   char *str;\n   DilloHtmlInput *input;\n   int i;\n\n   if (html->InFlags & IN_TEXTAREA && !S_TOP(html)->display_none) {\n      /* Remove the line ending that follows the opening tag */\n      if (html->Stash->str[0] == '\\r')\n         dStr_erase(html->Stash, 0, 1);\n      if (html->Stash->str[0] == '\\n')\n         dStr_erase(html->Stash, 0, 1);\n\n      /* As the spec recommends to canonicalize line endings, it is safe\n       * to replace '\\r' with '\\n'. It will be canonicalized anyway! */\n      for (i = 0; i < html->Stash->len; ++i) {\n         if (html->Stash->str[i] == '\\r') {\n            if (html->Stash->str[i + 1] == '\\n')\n               dStr_erase(html->Stash, i, 1);\n            else\n               html->Stash->str[i] = '\\n';\n         }\n      }\n\n      /* The HTML3.2 spec says it can have \"text and character entities\". */\n      str = a_Html_parse_entities(html, html->Stash->str, html->Stash->len);\n      input = Html_get_current_input(html);\n      if (input) {\n         input->init_str = str;\n         ((MultiLineTextResource *)input->embed->getResource ())->setText(str);\n      }\n\n   }\n   html->InFlags &= ~IN_TEXTAREA;\n}\n\n/*\n * <SELECT>\n */\n/* The select tag is quite tricky, because of gorpy html syntax. */\nvoid Html_tag_open_select(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   int rows = 0;\n\n   assert((html->InFlags & (IN_BUTTON | IN_SELECT | IN_TEXTAREA)) == 0);\n\n   html->InFlags |= IN_SELECT;\n   html->InFlags &= ~IN_OPTION;\n\n   char *name = a_Html_get_attr_wdef(html, tag, tagsize, \"name\", NULL);\n   ResourceFactory *factory = HT2LT(html)->getResourceFactory ();\n   DilloHtmlInputType type;\n   SelectionResource *res;\n   bool multi = a_Html_get_attr(html, tag, tagsize, \"multiple\") != NULL;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"size\"))) {\n      rows = strtol(attrbuf, NULL, 10);\n      if (rows > 100)\n         rows = 100;\n   }\n   if (rows < 1)\n      rows = multi ? 10 : 1;\n\n   if (rows == 1 && multi == false) {\n      type = DILLO_HTML_INPUT_SELECT;\n      res = factory->createOptionMenuResource ();\n   } else {\n      ListResource::SelectionMode mode;\n\n      type = DILLO_HTML_INPUT_SEL_LIST;\n      mode = multi ? ListResource::SELECTION_MULTIPLE\n                   : ListResource::SELECTION_AT_MOST_ONE;\n      res = factory->createListResource (mode, rows);\n   }\n   Embed *embed = new Embed(res);\n\n   if (prefs.show_tooltip &&\n       (attrbuf = a_Html_get_attr(html, tag, tagsize, \"title\"))) {\n\n      html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,\n                                        attrbuf);\n   }\n   HT2TB(html)->addWidget (embed, html->backgroundStyle ());\n\n   Html_add_input(html, type, embed, name, NULL, false);\n   a_Html_stash_init(html);\n   dFree(name);\n}\n\n/*\n * ?\n */\nvoid Html_tag_close_select(DilloHtml *html)\n{\n   if (html->InFlags & IN_SELECT) {\n      if (html->InFlags & IN_OPTION)\n         Html_option_finish(html);\n      html->InFlags &= ~IN_SELECT;\n      html->InFlags &= ~IN_OPTION;\n\n      DilloHtmlInput *input = Html_get_current_input(html);\n      if (input) {\n         DilloHtmlSelect *select = input->select;\n         if (input->type == DILLO_HTML_INPUT_SELECT) {\n            // option menu interface requires that something be selected */\n            select->ensureSelection ();\n         }\n         select->addOptsTo ((SelectionResource*)input->embed->getResource());\n      }\n   }\n}\n\nvoid Html_tag_open_optgroup(DilloHtml *html, const char *tag, int tagsize)\n{\n   if (!(html->InFlags & IN_SELECT)) {\n      BUG_MSG(\"<optgroup> outside <select>.\");\n      return;\n   }\n   if (html->InFlags & IN_OPTGROUP) {\n      BUG_MSG(\"Nested <optgroup>.\");\n      return;\n   }\n   if (html->InFlags & IN_OPTION) {\n      Html_option_finish(html);\n      html->InFlags &= ~IN_OPTION;\n   }\n\n   html->InFlags |= IN_OPTGROUP;\n\n   DilloHtmlInput *input = Html_get_current_input(html);\n   if (input &&\n       (input->type == DILLO_HTML_INPUT_SELECT ||\n        input->type == DILLO_HTML_INPUT_SEL_LIST)) {\n      char *label = a_Html_get_attr_wdef(html, tag, tagsize, \"label\", NULL);\n      bool enabled = (a_Html_get_attr(html, tag, tagsize, \"disabled\") == NULL);\n\n      if (!label) {\n         BUG_MSG(\"<optgroup> requires label attribute.\");\n         label = strdup(\"\");\n      }\n\n      DilloHtmlOptgroup *opt =\n         new DilloHtmlOptgroup (label, enabled);\n\n      input->select->addOpt(opt);\n   }\n}\n\nvoid Html_tag_close_optgroup(DilloHtml *html)\n{\n   if (html->InFlags & IN_OPTGROUP) {\n      html->InFlags &= ~IN_OPTGROUP;\n\n      if (html->InFlags & IN_OPTION) {\n         Html_option_finish(html);\n         html->InFlags &= ~IN_OPTION;\n      }\n\n      DilloHtmlInput *input = Html_get_current_input(html);\n      if (input &&\n          (input->type == DILLO_HTML_INPUT_SELECT ||\n           input->type == DILLO_HTML_INPUT_SEL_LIST)) {\n         DilloHtmlOptgroupClose *opt = new DilloHtmlOptgroupClose ();\n\n         input->select->addOpt(opt);\n      }\n   }\n}\n\n/*\n * <OPTION>\n */\nvoid Html_tag_open_option(DilloHtml *html, const char *tag, int tagsize)\n{\n   if (!(html->InFlags & IN_SELECT)) {\n      BUG_MSG(\"<option> outside <select>.\");\n      return;\n   }\n   if (html->InFlags & IN_OPTION)\n      Html_option_finish(html);\n   html->InFlags |= IN_OPTION;\n\n   DilloHtmlInput *input = Html_get_current_input(html);\n   if (input &&\n       (input->type == DILLO_HTML_INPUT_SELECT ||\n        input->type == DILLO_HTML_INPUT_SEL_LIST)) {\n      char *value = a_Html_get_attr_wdef(html, tag, tagsize, \"value\", NULL);\n      char *label = a_Html_get_attr_wdef(html, tag, tagsize, \"label\", NULL);\n      bool selected = (a_Html_get_attr(html, tag, tagsize,\"selected\") != NULL);\n      bool enabled = (a_Html_get_attr(html, tag, tagsize, \"disabled\") == NULL);\n\n      DilloHtmlOption *option =\n         new DilloHtmlOption (value, label, selected, enabled);\n\n      input->select->addOpt(option);\n   }\n\n   a_Html_stash_init(html);\n}\n\nvoid Html_tag_close_option(DilloHtml *html)\n{\n   if (html->InFlags & IN_OPTION) {\n      Html_option_finish(html);\n      html->InFlags &= ~IN_OPTION;\n   }\n}\n\n/*\n * <BUTTON>\n */\nvoid Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize)\n{\n   /*\n    * Buttons are rendered on one line, this is (at several levels) a\n    * bit simpler. May be changed in the future.\n    */\n   DilloHtmlInputType inp_type;\n   char *type;\n\n   assert((html->InFlags & (IN_BUTTON | IN_SELECT | IN_TEXTAREA)) == 0);\n\n   html->InFlags |= IN_BUTTON;\n   type = a_Html_get_attr_wdef(html, tag, tagsize, \"type\", \"\");\n\n   if (!dStrAsciiCasecmp(type, \"button\")) {\n      inp_type = DILLO_HTML_INPUT_BUTTON;\n   } else if (!dStrAsciiCasecmp(type, \"reset\")) {\n      inp_type = DILLO_HTML_INPUT_BUTTON_RESET;\n   } else if (!dStrAsciiCasecmp(type, \"submit\") || !*type) {\n      /* submit button is the default */\n      inp_type = DILLO_HTML_INPUT_BUTTON_SUBMIT;\n   } else {\n      inp_type = DILLO_HTML_INPUT_UNKNOWN;\n      BUG_MSG(\"<button> type unknown: '%s'.\", type);\n   }\n\n   if (inp_type != DILLO_HTML_INPUT_UNKNOWN) {\n      /* Render the button */\n      Widget *page;\n      Embed *embed;\n      const char *attrbuf;\n      char *name, *value;\n\n      if (prefs.show_tooltip &&\n          (attrbuf = a_Html_get_attr(html, tag, tagsize, \"title\"))) {\n\n         html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,\n                                           attrbuf);\n      }\n      /* We used to have Textblock (prefs.limit_text_width, ...) here,\n       * but it caused 100% CPU usage.\n       */\n      page = new Textblock (false, true);\n      page->setStyle (html->backgroundStyle ());\n\n      ResourceFactory *factory = HT2LT(html)->getResourceFactory();\n      Resource *resource = factory->createComplexButtonResource(page, true);\n      embed = new Embed(resource);\n// a_Dw_button_set_sensitive (DW_BUTTON (button), FALSE);\n\n      HT2TB(html)->addWidget (embed, html->backgroundStyle ());\n\n      S_TOP(html)->textblock = html->dw = page;\n\n      value = a_Html_get_attr_wdef(html, tag, tagsize, \"value\", NULL);\n      name = a_Html_get_attr_wdef(html, tag, tagsize, \"name\", NULL);\n\n      Html_add_input(html, inp_type, embed, name, value, FALSE);\n      dFree(name);\n      dFree(value);\n   }\n   dFree(type);\n}\n\n/*\n * Handle close <BUTTON>\n */\nvoid Html_tag_close_button(DilloHtml *html)\n{\n   html->InFlags &= ~IN_BUTTON;\n}\n\n/*\n * Class implementations\n */\n\n/*\n * DilloHtmlForm\n */\n\n/*\n * Constructor\n */\nDilloHtmlForm::DilloHtmlForm (DilloHtml *html2,\n                              DilloHtmlMethod method2,\n                              const DilloUrl *action2,\n                              DilloHtmlEnc content_type2,\n                              const char *charset, bool enabled)\n{\n   html = html2;\n   method = method2;\n   action = a_Url_dup(action2);\n   content_type = content_type2;\n   submit_charset = dStrdup(charset);\n   inputs = new misc::SimpleVector <DilloHtmlInput*> (4);\n   num_entry_fields = 0;\n   showing_hiddens = false;\n   this->enabled = enabled;\n   form_receiver = new DilloHtmlReceiver (this);\n}\n\n/*\n * Destructor\n */\nDilloHtmlForm::~DilloHtmlForm ()\n{\n   a_Url_free(action);\n   dFree(submit_charset);\n   for (int j = 0; j < inputs->size(); j++)\n      delete inputs->get(j);\n   delete(inputs);\n   if (form_receiver)\n      delete(form_receiver);\n}\n\nvoid DilloHtmlForm::eventHandler(Resource *resource, EventButton *event)\n{\n   _MSG(\"DilloHtmlForm::eventHandler\\n\");\n   if (event && (event->button == 3)) {\n      a_UIcmd_form_popup(html->bw, html->page_url, this, showing_hiddens);\n   } else {\n      DilloHtmlInput *input = getInput(resource);\n      if (input) {\n         input->activate (this, num_entry_fields, event);\n      } else {\n        MSG(\"DilloHtmlForm::eventHandler: ERROR, input not found!\\n\");\n      }\n   }\n}\n\n/*\n * Submit.\n * (Called by eventHandler())\n */\nvoid DilloHtmlForm::submit(DilloHtmlInput *active_input, EventButton *event)\n{\n   if (!dStrAsciiCasecmp(URL_SCHEME(html->page_url), \"https\") &&\n       dStrAsciiCasecmp(URL_SCHEME(action), \"https\")) {\n      int choice = a_Dialog_choice(\"Dillo: Insecure form submission\",\n                                   \"A form on a SECURE page wants to use an \"\n                                   \"INSECURE protocol to submit data.\",\n                                   \"Continue\", \"Cancel\", NULL);\n      if (choice != 1)\n         return;\n   }\n\n   DilloUrl *url = buildQueryUrl(active_input);\n   if (url) {\n      if (event && event->button == 2) {\n         if (prefs.middle_click_opens_new_tab) {\n            int focus = prefs.focus_new_tab ? 1 : 0;\n            if (event->state == SHIFT_MASK) focus = !focus;\n            a_UIcmd_open_url_nt(html->bw, url, focus);\n         } else {\n            a_UIcmd_open_url_nw(html->bw, url);\n         }\n      } else {\n         a_UIcmd_open_url(html->bw, url);\n      }\n      a_Url_free(url);\n   }\n}\n\n/*\n * Build a new query URL.\n * (Called by submit())\n */\nDilloUrl *DilloHtmlForm::buildQueryUrl(DilloHtmlInput *active_input)\n{\n   DilloUrl *new_url = NULL;\n\n   if ((method == DILLO_HTML_METHOD_GET) ||\n       (method == DILLO_HTML_METHOD_POST)) {\n      Dstr *DataStr;\n      DilloHtmlInput *active_submit = NULL;\n\n      _MSG(\"DilloHtmlForm::buildQueryUrl: action=%s\\n\",URL_STR_(action));\n\n      if (active_input) {\n         if ((active_input->type == DILLO_HTML_INPUT_SUBMIT) ||\n             (active_input->type == DILLO_HTML_INPUT_IMAGE) ||\n             (active_input->type == DILLO_HTML_INPUT_BUTTON_SUBMIT)) {\n            active_submit = active_input;\n         }\n      }\n\n      DataStr = buildQueryData(active_submit);\n      if (DataStr) {\n         /* action was previously resolved against base URL */\n         char *action_str = dStrdup(URL_STR(action));\n\n         if (method == DILLO_HTML_METHOD_POST) {\n            new_url = a_Url_new(action_str, NULL);\n            /* new_url keeps the dStr and sets DataStr to NULL */\n            a_Url_set_data(new_url, &DataStr);\n            a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Post);\n            if (content_type == DILLO_HTML_ENC_MULTIPART)\n               a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_MultipartEnc);\n         } else {\n            /* remove <fragment> and <query> sections if present */\n            char *url_str, *p;\n            if ((p = strchr(action_str, '#')))\n               *p = 0;\n            if ((p = strchr(action_str, '?')))\n               *p = 0;\n\n            url_str = dStrconcat(action_str, \"?\", DataStr->str, NULL);\n            new_url = a_Url_new(url_str, NULL);\n            a_Url_set_flags(new_url, URL_FLAGS(new_url) | URL_Get);\n            dFree(url_str);\n         }\n         dStr_free(DataStr, 1);\n         dFree(action_str);\n      }\n   } else {\n      MSG(\"DilloHtmlForm::buildQueryUrl: Method unknown\\n\");\n   }\n\n   return new_url;\n}\n\n/*\n * Construct the data for a query URL\n */\nDstr *DilloHtmlForm::buildQueryData(DilloHtmlInput *active_submit)\n{\n   Dstr *DataStr = NULL;\n   char *boundary = NULL;\n   iconv_t char_encoder = (iconv_t) -1;\n\n   if (submit_charset && dStrAsciiCasecmp(submit_charset, \"UTF-8\")) {\n      /* Some iconv implementations, given \"//TRANSLIT\", will do their best to\n       * transliterate the string. Under the circumstances, doing so is likely\n       * for the best.\n       */\n      char *translit = dStrconcat(submit_charset, \"//TRANSLIT\", NULL);\n\n      char_encoder = iconv_open(translit, \"UTF-8\");\n      dFree(translit);\n\n      if (char_encoder == (iconv_t) -1)\n         char_encoder = iconv_open(submit_charset, \"UTF-8\");\n\n      if (char_encoder == (iconv_t) -1) {\n         MSG_WARN(\"Cannot convert to character encoding '%s'\\n\",\n                  submit_charset);\n      } else {\n         MSG(\"Form character encoding: '%s'\\n\", submit_charset);\n      }\n   }\n\n   if (content_type == DILLO_HTML_ENC_MULTIPART) {\n      if (!(boundary = makeMultipartBoundary(char_encoder, active_submit)))\n         MSG_ERR(\"Cannot generate multipart/form-data boundary.\\n\");\n   }\n\n   if ((content_type == DILLO_HTML_ENC_URLENCODED) || (boundary != NULL)) {\n      Dlist *values = dList_new(5);\n\n      DataStr = dStr_sized_new(4096);\n      for (int i = 0; i < inputs->size(); i++) {\n         DilloHtmlInput *input = inputs->get (i);\n         Dstr *name = dStr_new(input->name);\n         bool is_active_submit = (input == active_submit);\n         int valcount;\n\n         name = encodeText(char_encoder, &name);\n\n         input->appendValuesTo(values, is_active_submit);\n\n         if ((valcount = dList_length(values)) > 0) {\n            if (input->type == DILLO_HTML_INPUT_FILE) {\n               if (valcount > 1)\n                  MSG_WARN(\"multiple files per form control not supported\\n\");\n               Dstr *file = (Dstr *) dList_nth_data(values, 0);\n               dList_remove(values, file);\n\n               /* Get filename and encode it. Do not encode file contents. */\n               LabelButtonResource *lbr =\n                            (LabelButtonResource*) input->embed->getResource();\n               const char *filename = lbr->getLabel();\n               if (filename[0] && strcmp(filename, input->init_str)) {\n                  const char *p = strrchr(filename, '/');\n                  if (p)\n                     filename = p + 1;     /* don't reveal path */\n                  Dstr *dfilename = dStr_new(filename);\n                  dfilename = encodeText(char_encoder, &dfilename);\n                  filesInputMultipartAppend(DataStr, boundary, name->str,\n                                            file, dfilename->str);\n                  dStr_free(dfilename, 1);\n               }\n               dStr_free(file, 1);\n            } else if (input->type == DILLO_HTML_INPUT_INDEX) {\n               /* no name */\n               Dstr *val = (Dstr *) dList_nth_data(values, 0);\n               dList_remove(values, val);\n               val = encodeText(char_encoder, &val);\n               strUrlencodeAppend(DataStr, val->str);\n               dStr_free(val, 1);\n            } else if (input->type == DILLO_HTML_INPUT_IMAGE) {\n               Dstr *x, *y;\n               x = (Dstr *) dList_nth_data(values, 0);\n               dList_remove(values, x);\n               y = (Dstr *) dList_nth_data(values, 0);\n               dList_remove(values, y);\n               if (content_type == DILLO_HTML_ENC_URLENCODED)\n                  imageInputUrlencodeAppend(DataStr, name, x, y);\n               else if (content_type == DILLO_HTML_ENC_MULTIPART)\n                  imageInputMultipartAppend(DataStr, boundary, name, x, y);\n               dStr_free(x, 1);\n               dStr_free(y, 1);\n            } else {\n               for (int j = 0; j < valcount; j++) {\n                  Dstr *val = (Dstr *) dList_nth_data(values, 0);\n                  dList_remove(values, val);\n                  val = encodeText(char_encoder, &val);\n                  if (content_type == DILLO_HTML_ENC_URLENCODED)\n                     inputUrlencodeAppend(DataStr, name->str, val->str);\n                  else if (content_type == DILLO_HTML_ENC_MULTIPART)\n                     inputMultipartAppend(DataStr, boundary, name->str,\n                                          val->str);\n                  dStr_free(val, 1);\n               }\n            }\n         }\n         dStr_free(name, 1);\n      }\n      if (DataStr->len > 0) {\n         if (content_type == DILLO_HTML_ENC_URLENCODED) {\n            if (DataStr->str[DataStr->len - 1] == '&')\n               dStr_truncate(DataStr, DataStr->len - 1);\n         } else if (content_type == DILLO_HTML_ENC_MULTIPART) {\n            dStr_append(DataStr, \"--\");\n         }\n      }\n      dList_free(values);\n   }\n   dFree(boundary);\n   if (char_encoder != (iconv_t) -1)\n      (void)iconv_close(char_encoder);\n   return DataStr;\n}\n\n/*\n * Generate a boundary string for use in separating the parts of a\n * multipart/form-data submission.\n */\nchar *DilloHtmlForm::makeMultipartBoundary(iconv_t char_encoder,\n                                           DilloHtmlInput *active_submit)\n{\n   const int max_tries = 10;\n   Dlist *values = dList_new(5);\n   Dstr *DataStr = dStr_new(\"\");\n   Dstr *boundary = dStr_new(\"\");\n   char *ret = NULL;\n\n   /* fill DataStr with names, filenames, and values */\n   for (int i = 0; i < inputs->size(); i++) {\n      Dstr *dstr;\n      DilloHtmlInput *input = inputs->get (i);\n      bool is_active_submit = (input == active_submit);\n      input->appendValuesTo(values, is_active_submit);\n\n      if (input->name) {\n         dstr = dStr_new(input->name);\n         dstr = encodeText(char_encoder, &dstr);\n         dStr_append_l(DataStr, dstr->str, dstr->len);\n         dStr_free(dstr, 1);\n      }\n      if (input->type == DILLO_HTML_INPUT_FILE) {\n         LabelButtonResource *lbr =\n            (LabelButtonResource*)input->embed->getResource();\n         const char *filename = lbr->getLabel();\n         if (filename[0] && strcmp(filename, input->init_str)) {\n            dstr = dStr_new(filename);\n            dstr = encodeText(char_encoder, &dstr);\n            dStr_append_l(DataStr, dstr->str, dstr->len);\n            dStr_free(dstr, 1);\n         }\n      }\n      int length = dList_length(values);\n      for (int i = 0; i < length; i++) {\n         dstr = (Dstr *) dList_nth_data(values, 0);\n         dList_remove(values, dstr);\n         if (input->type != DILLO_HTML_INPUT_FILE)\n            dstr = encodeText(char_encoder, &dstr);\n         dStr_append_l(DataStr, dstr->str, dstr->len);\n         dStr_free(dstr, 1);\n      }\n   }\n\n   /* generate a boundary that is not contained within the data */\n   for (int i = 0; i < max_tries && !ret; i++) {\n      // Firefox-style boundary\n      dStr_sprintf(boundary, \"---------------------------%d%d%d\",\n                   rand(), rand(), rand());\n      dStr_truncate(boundary, 70);\n      if (dStr_memmem(DataStr, boundary) == NULL)\n         ret = boundary->str;\n   }\n   dList_free(values);\n   dStr_free(DataStr, 1);\n   dStr_free(boundary, (ret == NULL));\n   return ret;\n}\n\n/*\n * Pass input text through character set encoder.\n * Return value: same input Dstr if no encoding is needed.\n *               new Dstr when encoding (input Dstr is freed).\n */\nDstr *DilloHtmlForm::encodeText(iconv_t char_encoder, Dstr **input)\n{\n   int rc = 0;\n   Dstr *output;\n   const int bufsize = 128;\n   inbuf_t *inPtr;\n   char *buffer, *outPtr;\n   size_t inLeft, outRoom;\n   bool bad_chars = false;\n\n   if ((char_encoder == (iconv_t) -1) || *input == NULL || (*input)->len == 0)\n      return *input;\n\n   output = dStr_new(\"\");\n   inPtr  = (*input)->str;\n   inLeft = (*input)->len;\n   buffer = dNew(char, bufsize);\n\n   while ((rc != EINVAL) && (inLeft > 0)) {\n\n      outPtr = buffer;\n      outRoom = bufsize;\n\n      rc = iconv(char_encoder, &inPtr, &inLeft, &outPtr, &outRoom);\n\n      // iconv() on success, number of bytes converted\n      //         -1, errno == EILSEQ illegal byte sequence found\n      //                      EINVAL partial character ends source buffer\n      //                      E2BIG  destination buffer is full\n      //\n      // GNU iconv has the undocumented(!) behavior that EILSEQ is also\n      // returned when a character cannot be converted.\n\n      dStr_append_l(output, buffer, bufsize - outRoom);\n\n      if (rc == -1) {\n         rc = errno;\n      }\n      if (rc == EILSEQ){\n         /* count chars? (would be utf-8-specific) */\n         bad_chars = true;\n         inPtr++;\n         inLeft--;\n         dStr_append_c(output, '?');\n      } else if (rc == EINVAL) {\n         MSG_ERR(\"Form encode text: bad source string.\\n\");\n      }\n   }\n\n   if (bad_chars) {\n      /*\n       * It might be friendly to inform the caller, who would know whether\n       * it is safe to display the beginning of the string in a message\n       * (isn't, e.g., a password).\n       */\n      MSG_WARN(\"Form encode text: string cannot be converted cleanly.\\n\");\n   }\n\n   dFree(buffer);\n   dStr_free(*input, 1);\n\n   return output;\n}\n\n/*\n * Urlencode 'str' and append it to 'dstr'\n */\nvoid DilloHtmlForm::strUrlencodeAppend(Dstr *dstr, const char *str)\n{\n   char *encoded = a_Url_encode_hex_str(str);\n   dStr_append(dstr, encoded);\n   dFree(encoded);\n}\n\n/*\n * Append a name-value pair to url data using url encoding.\n */\nvoid DilloHtmlForm::inputUrlencodeAppend(Dstr *data, const char *name,\n                                         const char *value)\n{\n   if (name && name[0]) {\n      strUrlencodeAppend(data, name);\n      dStr_append_c(data, '=');\n      strUrlencodeAppend(data, value);\n      dStr_append_c(data, '&');\n   }\n}\n\n/*\n * Append files to URL data using multipart encoding.\n * Currently only accepts one file.\n */\nvoid DilloHtmlForm::filesInputMultipartAppend(Dstr* data,\n                                              const char *boundary,\n                                              const char *name,\n                                              Dstr *file,\n                                              const char *filename)\n{\n   const char *ctype, *ext;\n\n   if (name && name[0]) {\n      (void)a_Misc_get_content_type_from_data(file->str, file->len, &ctype);\n      /* Heuristic: text/plain with \".htm[l]\" extension -> text/html */\n      if ((ext = strrchr(filename, '.')) &&\n          !dStrAsciiCasecmp(ctype, \"text/plain\") &&\n          (!dStrAsciiCasecmp(ext, \".html\") || !dStrAsciiCasecmp(ext, \".htm\"))){\n         ctype = \"text/html\";\n      }\n\n      if (data->len == 0) {\n         dStr_append(data, \"--\");\n         dStr_append(data, boundary);\n      }\n      dStr_sprintfa(data,\n                    \"\\r\\n\"\n                    \"Content-Disposition: form-data; name=\\\"%s\\\"; \"\n                       \"filename=\\\"\", name);\n      /*\n       * Replace the characters that are the most likely to damage things.\n       * For a while, there was some momentum to standardize on an encoding,\n       * but HTML5/Ian Hickson/his Google masters are, as of late 2012,\n       * evidently standing in opposition to all of that for some reason.\n       */\n      for (int i = 0; char c = filename[i]; i++) {\n         if (c == '\\\"' || c == '\\r' || c == '\\n')\n            c = '_';\n         dStr_append_c(data, c);\n      }\n      dStr_sprintfa(data,\n                    \"\\\"\\r\\n\"\n                    \"Content-Type: %s\\r\\n\"\n                    \"\\r\\n\", ctype);\n\n      dStr_append_l(data, file->str, file->len);\n\n      dStr_sprintfa(data,\n                    \"\\r\\n\"\n                    \"--%s\", boundary);\n   }\n}\n\n/*\n * Append a name-value pair to url data using multipart encoding.\n */\nvoid DilloHtmlForm::inputMultipartAppend(Dstr *data,\n                                         const char *boundary,\n                                         const char *name,\n                                         const char *value)\n{\n   if (name && name[0]) {\n      if (data->len == 0) {\n         dStr_append(data, \"--\");\n         dStr_append(data, boundary);\n      }\n      dStr_sprintfa(data,\n                    \"\\r\\n\"\n                    \"Content-Disposition: form-data; name=\\\"%s\\\"\\r\\n\"\n                    \"\\r\\n\"\n                    \"%s\\r\\n\"\n                    \"--%s\",\n                    name, value, boundary);\n   }\n}\n\n/*\n * Append an image button click position to url data using url encoding.\n */\nvoid DilloHtmlForm::imageInputUrlencodeAppend(Dstr *data, Dstr *name, Dstr *x,\n                                              Dstr *y)\n{\n   if (name->len) {\n      strUrlencodeAppend(data, name->str);\n      dStr_sprintfa(data, \".x=%s&\", x->str);\n      strUrlencodeAppend(data, name->str);\n      dStr_sprintfa(data, \".y=%s&\", y->str);\n   } else\n      dStr_sprintfa(data, \"x=%s&y=%s&\", x->str, y->str);\n}\n\n/*\n * Append an image button click position to url data using multipart encoding.\n */\nvoid DilloHtmlForm::imageInputMultipartAppend(Dstr *data, const char *boundary,\n                                              Dstr *name, Dstr *x, Dstr *y)\n{\n   int orig_len = name->len;\n\n   if (orig_len)\n      dStr_append_c(name, '.');\n   dStr_append_c(name, 'x');\n\n   inputMultipartAppend(data, boundary, name->str, x->str);\n   dStr_truncate(name, name->len - 1);\n   dStr_append_c(name, 'y');\n   inputMultipartAppend(data, boundary, name->str, y->str);\n   dStr_truncate(name, orig_len);\n}\n\n/*\n * Reset all inputs containing reset to their initial values.  In\n * general, reset is the reset button for the form.\n */\nvoid DilloHtmlForm::reset ()\n{\n   int size = inputs->size();\n   for (int i = 0; i < size; i++)\n      inputs->get(i)->reset();\n}\n\n/*\n * Show/hide \"hidden\" form controls\n */\nvoid DilloHtmlForm::display_hiddens(bool display)\n{\n   int size = inputs->size();\n   for (int i = 0; i < size; i++) {\n      DilloHtmlInput *input = inputs->get(i);\n      if (input->type == DILLO_HTML_INPUT_HIDDEN) {\n         input->embed->setDisplayed(display);\n      }\n   }\n  showing_hiddens = display;\n}\n\nvoid DilloHtmlForm::setEnabled(bool enabled)\n{\n   for (int i = 0; i < inputs->size(); i++)\n      inputs->get(i)->setEnabled(enabled);\n}\n\n/*\n * Add a new input.\n */\nvoid DilloHtmlForm::addInput(DilloHtmlInput *input, DilloHtmlInputType type)\n{\n   input->connectTo (form_receiver);\n   input->setEnabled (enabled);\n   int ni = inputs->size ();\n   inputs->increase ();\n   inputs->set (ni,input);\n\n   /* some stats */\n   if (type == DILLO_HTML_INPUT_PASSWORD ||\n       type == DILLO_HTML_INPUT_TEXT) {\n      num_entry_fields++;\n   }\n}\n\n/*\n * Return the input with a given resource.\n */\nDilloHtmlInput *DilloHtmlForm::getInput (Resource *resource)\n{\n   for (int idx = 0; idx < inputs->size(); idx++) {\n      DilloHtmlInput *input = inputs->get(idx);\n      if (input->embed &&\n          resource == input->embed->getResource())\n         return input;\n   }\n   return NULL;\n}\n\n/*\n * Return a Radio input for the given name.\n */\nDilloHtmlInput *DilloHtmlForm::getRadioInput (const char *name)\n{\n   for (int idx = 0; idx < inputs->size(); idx++) {\n      DilloHtmlInput *input = inputs->get(idx);\n      if (input->type == DILLO_HTML_INPUT_RADIO &&\n          input->name && !dStrAsciiCasecmp(input->name, name))\n         return input;\n   }\n   return NULL;\n}\n\n/*\n * DilloHtmlReceiver\n *\n * TODO: Currently there's \"clicked\" for buttons, we surely need \"enter\" for\n * textentries, and maybe the \"mouseover, ....\" set for Javascript.\n */\n\nvoid DilloHtmlReceiver::activate (Resource *resource)\n{\n   form->eventHandler(resource, NULL);\n}\n\n/*\n * Enter a form control, as in \"onmouseover\".\n * For _pressing_ enter in a text control, see activate().\n */\nvoid DilloHtmlReceiver::enter (Resource *resource)\n{\n   DilloHtml *html = form->html;\n   DilloHtmlInput *input = form->getInput(resource);\n   const char *msg = \"\";\n\n   if ((input->type == DILLO_HTML_INPUT_SUBMIT) ||\n       (input->type == DILLO_HTML_INPUT_IMAGE) ||\n       (input->type == DILLO_HTML_INPUT_BUTTON_SUBMIT) ||\n       (input->type == DILLO_HTML_INPUT_INDEX) ||\n       ((prefs.enterpress_forces_submit || form->num_entry_fields == 1) &&\n        ((input->type == DILLO_HTML_INPUT_PASSWORD) ||\n         (input->type == DILLO_HTML_INPUT_TEXT)))) {\n      /* The control can submit form. Show action URL. */\n      msg = URL_STR(form->action);\n   }\n   a_UIcmd_set_msg(html->bw, \"%s\", msg);\n}\n\n/*\n * Leave a form control, or \"onmouseout\".\n */\nvoid DilloHtmlReceiver::leave (Resource *resource)\n{\n   DilloHtml *html = form->html;\n   a_UIcmd_set_msg(html->bw, \"\");\n}\n\nvoid DilloHtmlReceiver::clicked (Resource *resource,\n                                 EventButton *event)\n{\n   form->eventHandler(resource, event);\n}\n\n/*\n * DilloHtmlInput\n */\n\n/*\n * Constructor\n */\nDilloHtmlInput::DilloHtmlInput (DilloHtmlInputType type2, Embed *embed2,\n                                const char *name2, const char *init_str2,\n                                bool init_val2)\n{\n   type = type2;\n   embed = embed2;\n   name = (name2) ? dStrdup(name2) : NULL;\n   init_str = (init_str2) ? dStrdup(init_str2) : NULL;\n   init_val = init_val2;\n   select = NULL;\n   switch (type) {\n   case DILLO_HTML_INPUT_SELECT:\n   case DILLO_HTML_INPUT_SEL_LIST:\n      select = new DilloHtmlSelect;\n      break;\n   default:\n      break;\n   }\n   file_data = NULL;\n   reset ();\n}\n\n/*\n * Destructor\n */\nDilloHtmlInput::~DilloHtmlInput ()\n{\n   dFree(name);\n   dFree(init_str);\n   dStr_free(file_data, 1);\n   if (select)\n      delete select;\n}\n\n/*\n * Connect to a receiver.\n */\nvoid DilloHtmlInput::connectTo(DilloHtmlReceiver *form_receiver)\n{\n   Resource *resource;\n   if (embed && (resource = embed->getResource())) {\n      resource->connectClicked (form_receiver);\n      if (type == DILLO_HTML_INPUT_SUBMIT ||\n          type == DILLO_HTML_INPUT_RESET ||\n          type == DILLO_HTML_INPUT_BUTTON_SUBMIT ||\n          type == DILLO_HTML_INPUT_BUTTON_RESET ||\n          type == DILLO_HTML_INPUT_IMAGE ||\n          type == DILLO_HTML_INPUT_FILE ||\n          type == DILLO_HTML_INPUT_TEXT ||\n          type == DILLO_HTML_INPUT_PASSWORD ||\n          type == DILLO_HTML_INPUT_INDEX) {\n         resource->connectActivate (form_receiver);\n      }\n   }\n}\n\n/*\n * Activate a form\n */\nvoid DilloHtmlInput::activate(DilloHtmlForm *form, int num_entry_fields,\n                              EventButton *event)\n{\n   switch (type) {\n   case DILLO_HTML_INPUT_FILE:\n      readFile (form->html->bw);\n      break;\n   case DILLO_HTML_INPUT_RESET:\n   case DILLO_HTML_INPUT_BUTTON_RESET:\n      form->reset();\n      break;\n   case DILLO_HTML_INPUT_TEXT:\n   case DILLO_HTML_INPUT_PASSWORD:\n      if (!(prefs.enterpress_forces_submit || num_entry_fields == 1)) {\n         break;\n      } else {\n         /* fall through */\n      }\n   case DILLO_HTML_INPUT_SUBMIT:\n   case DILLO_HTML_INPUT_BUTTON_SUBMIT:\n   case DILLO_HTML_INPUT_IMAGE:\n   case DILLO_HTML_INPUT_INDEX:\n      form->submit(this, event);\n      break;\n   default:\n      break;\n   }\n}\n\n/*\n * Read a file into cache\n */\nvoid DilloHtmlInput::readFile (BrowserWindow *bw)\n{\n   const char *filename = a_UIcmd_select_file();\n   if (filename) {\n      a_UIcmd_set_msg(bw, \"Loading file...\");\n      dStr_free(file_data, 1);\n      file_data = a_Misc_file2dstr(filename);\n      if (file_data) {\n         a_UIcmd_set_msg(bw, \"File loaded.\");\n         LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();\n         lbr->setLabel(filename);\n      } else {\n         a_UIcmd_set_msg(bw, \"ERROR: can't load: %s\", filename);\n      }\n   }\n}\n\n/*\n * Get the values for a \"successful control\".\n */\nvoid DilloHtmlInput::appendValuesTo(Dlist *values, bool is_active_submit)\n{\n   switch (type) {\n   case DILLO_HTML_INPUT_TEXT:\n   case DILLO_HTML_INPUT_PASSWORD:\n   case DILLO_HTML_INPUT_INDEX:\n   case DILLO_HTML_INPUT_HIDDEN:\n      {\n         EntryResource *entryres = (EntryResource*)embed->getResource();\n         dList_append(values, dStr_new(entryres->getText()));\n      }\n      break;\n   case DILLO_HTML_INPUT_TEXTAREA:\n      {\n         MultiLineTextResource *textres =\n            (MultiLineTextResource*)embed->getResource();\n         dList_append(values, dStr_new(textres->getText()));\n      }\n      break;\n   case DILLO_HTML_INPUT_CHECKBOX:\n   case DILLO_HTML_INPUT_RADIO:\n      {\n         ToggleButtonResource *cb_r =\n            (ToggleButtonResource*)embed->getResource();\n         if (name && init_str && cb_r->isActivated()) {\n            dList_append(values, dStr_new(init_str));\n         }\n      }\n      break;\n   case DILLO_HTML_INPUT_SUBMIT:\n   case DILLO_HTML_INPUT_BUTTON_SUBMIT:\n      if (is_active_submit)\n         dList_append(values, dStr_new(init_str));\n      break;\n   case DILLO_HTML_INPUT_SELECT:\n   case DILLO_HTML_INPUT_SEL_LIST:\n      {\n         SelectionResource *sel_res = (SelectionResource*)embed->getResource();\n         select->appendValuesTo (values, sel_res);\n      }\n      break;\n   case DILLO_HTML_INPUT_FILE:\n      {\n         LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();\n         const char *filename = lbr->getLabel();\n         if (filename[0] && strcmp(filename, init_str)) {\n            if (file_data) {\n               Dstr *file = dStr_sized_new(file_data->len);\n               dStr_append_l(file, file_data->str, file_data->len);\n               dList_append(values, file);\n            } else {\n               MSG(\"FORM file input \\\"%s\\\" not loaded.\\n\", filename);\n            }\n         }\n      }\n      break;\n   case DILLO_HTML_INPUT_IMAGE:\n      if (is_active_submit) {\n         ComplexButtonResource *cbr =\n            (ComplexButtonResource*)embed->getResource();\n         Dstr *strX = dStr_new(\"\");\n         Dstr *strY = dStr_new(\"\");\n         dStr_sprintf(strX, \"%d\", cbr->getClickX());\n         dStr_sprintf(strY, \"%d\", cbr->getClickY());\n         dList_append(values, strX);\n         dList_append(values, strY);\n      }\n      break;\n   default:\n      break;\n   }\n}\n\n/*\n * Reset to the initial value.\n */\nvoid DilloHtmlInput::reset ()\n{\n   switch (type) {\n   case DILLO_HTML_INPUT_TEXT:\n   case DILLO_HTML_INPUT_PASSWORD:\n   case DILLO_HTML_INPUT_INDEX:\n   case DILLO_HTML_INPUT_HIDDEN:\n      {\n         EntryResource *entryres = (EntryResource*)embed->getResource();\n         entryres->setText(init_str ? init_str : \"\");\n      }\n      break;\n   case DILLO_HTML_INPUT_CHECKBOX:\n   case DILLO_HTML_INPUT_RADIO:\n      {\n         ToggleButtonResource *tb_r =\n            (ToggleButtonResource*)embed->getResource();\n         tb_r->setActivated(init_val);\n      }\n      break;\n   case DILLO_HTML_INPUT_SELECT:\n   case DILLO_HTML_INPUT_SEL_LIST:\n      if (select != NULL) {\n         SelectionResource *sr = (SelectionResource *) embed->getResource();\n         select->reset(sr);\n      }\n      break;\n   case DILLO_HTML_INPUT_TEXTAREA:\n      if (init_str != NULL) {\n         MultiLineTextResource *textres =\n            (MultiLineTextResource*)embed->getResource();\n         textres->setText(init_str ? init_str : \"\");\n      }\n      break;\n   case DILLO_HTML_INPUT_FILE:\n      {\n         LabelButtonResource *lbr = (LabelButtonResource*)embed->getResource();\n         lbr->setLabel(init_str);\n      }\n      break;\n   default:\n      break;\n   }\n}\n\n/*\n * DilloHtmlSelect\n */\n\n/*\n * Constructor\n */\nDilloHtmlSelect::DilloHtmlSelect ()\n{\n   opts = new misc::SimpleVector<DilloHtmlOptbase *> (4);\n}\n\n/*\n * Destructor\n */\nDilloHtmlSelect::~DilloHtmlSelect ()\n{\n   int size = opts->size ();\n   for (int k = 0; k < size; k++)\n      delete opts->get (k);\n   delete opts;\n}\n\nDilloHtmlOptbase *DilloHtmlSelect::getCurrentOpt ()\n{\n   return opts->get (opts->size() - 1);\n}\n\nvoid DilloHtmlSelect::addOpt (DilloHtmlOptbase *opt)\n{\n   int size = opts->size ();\n   opts->increase ();\n   opts->set (size, opt);\n}\n\n/*\n * Select the first option if nothing else is selected.\n */\nvoid DilloHtmlSelect::ensureSelection()\n{\n   int size = opts->size ();\n   if (size > 0) {\n      for (int i = 0; i < size; i++) {\n            DilloHtmlOptbase *opt = opts->get (i);\n            if (opt->isSelected())\n               return;\n      }\n      for (int i = 0; i < size; i++) {\n         DilloHtmlOptbase *opt = opts->get (i);\n         if (opt->select())\n            break;\n      }\n   }\n}\n\nvoid DilloHtmlSelect::addOptsTo (SelectionResource *res)\n{\n   int size = opts->size ();\n   for (int i = 0; i < size; i++) {\n      DilloHtmlOptbase *opt = opts->get (i);\n      opt->addSelf(res);\n   }\n}\n\nvoid DilloHtmlSelect::reset (SelectionResource *res)\n{\n   int size = opts->size ();\n   for (int i = 0; i < size; i++) {\n      DilloHtmlOptbase *opt = opts->get (i);\n      res->setItem(i, opt->isSelected());\n   }\n}\n\nvoid DilloHtmlSelect::appendValuesTo (Dlist *values, SelectionResource *res)\n{\n   int size = opts->size ();\n   for (int i = 0; i < size; i++) {\n      if (res->isSelected (i)) {\n         DilloHtmlOptbase *opt = opts->get (i);\n         const char *val = opt->getValue();\n\n         if (val)\n            dList_append(values, dStr_new(val));\n      }\n   }\n}\n\nDilloHtmlOptgroup::DilloHtmlOptgroup (char *label, bool enabled)\n{\n   this->label = label;\n   this->enabled = enabled;\n}\n\nDilloHtmlOptgroup::~DilloHtmlOptgroup ()\n{\n   dFree(label);\n}\n\n/*\n * DilloHtmlOption\n */\n\n/*\n * Constructor\n */\nDilloHtmlOption::DilloHtmlOption (char *value2, char *label2, bool selected2,\n                                  bool enabled2)\n{\n   value = value2;\n   label = label2;\n   content = NULL;\n   selected = selected2;\n   enabled = enabled2;\n}\n\n/*\n * Destructor\n */\nDilloHtmlOption::~DilloHtmlOption ()\n{\n   dFree(value);\n   dFree(label);\n   dFree(content);\n}\n\n/*\n * Utilities\n */\n\n/*\n * Create input image for the form\n */\nstatic Embed *Html_input_image(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloImage *Image;\n   Embed *button = NULL;\n\n   html->styleEngine->setPseudoLink ();\n\n   /* create new image and add it to the button */\n   a_Html_common_image_attrs(html, tag, tagsize);\n   if ((Image = a_Html_image_new(html, tag, tagsize))) {\n      // At this point, we know that Image->ir represents an image\n      // widget. Notice that the order of the casts matters, because\n      // of multiple inheritance.\n      dw::Image *dwi = (dw::Image*)(dw::core::ImgRenderer*)Image->img_rndr;\n      dwi->setStyle (html->backgroundStyle ());\n      ResourceFactory *factory = HT2LT(html)->getResourceFactory();\n      ComplexButtonResource *complex_b_r =\n         factory->createComplexButtonResource(dwi, false);\n      button = new Embed(complex_b_r);\n      HT2TB(html)->addWidget (button, html->style ());\n   }\n   if (!button)\n      MSG(\"Html_input_image: unable to create image submit.\\n\");\n   return button;\n}\n\n/*\n * ?\n */\nstatic void Html_option_finish(DilloHtml *html)\n{\n   DilloHtmlInput *input = Html_get_current_input(html);\n   if (input &&\n       (input->type == DILLO_HTML_INPUT_SELECT ||\n        input->type == DILLO_HTML_INPUT_SEL_LIST)) {\n      DilloHtmlOptbase *opt = input->select->getCurrentOpt ();\n      opt->setContent (html->Stash->str, html->Stash->len);\n   }\n}\n"
  },
  {
    "path": "src/form.hh",
    "content": "#ifndef __FORM_HH__\n#define __FORM_HH__\n\n#include \"url.h\"\n\n/*\n * Typedefs\n */\n\ntypedef enum {\n   DILLO_HTML_METHOD_UNKNOWN,\n   DILLO_HTML_METHOD_GET,\n   DILLO_HTML_METHOD_POST\n} DilloHtmlMethod;\n\ntypedef enum {\n   DILLO_HTML_ENC_URLENCODED,\n   DILLO_HTML_ENC_MULTIPART\n} DilloHtmlEnc;\n\n/*\n * Classes\n */\n\nclass DilloHtmlForm;\nclass DilloHtmlInput;\nclass DilloHtml;\n\n/*\n * Form API\n */\n\nDilloHtmlForm *a_Html_form_new(DilloHtml *html,\n                               DilloHtmlMethod method,\n                               const DilloUrl *action,\n                               DilloHtmlEnc enc,\n                               const char *charset, bool enabled);\n\nvoid a_Html_form_delete(DilloHtmlForm* form);\nvoid a_Html_input_delete(DilloHtmlInput* input);\nvoid a_Html_form_submit2(void *v_form);\nvoid a_Html_form_reset2(void *v_form);\nvoid a_Html_form_display_hiddens2(void *v_form, bool display);\n\n\n/*\n * Form parsing functions\n */\n\nvoid Html_tag_open_form(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_close_form(DilloHtml *html);\nvoid Html_tag_open_input(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_open_isindex(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_open_textarea(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_content_textarea(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_close_textarea(DilloHtml *html);\nvoid Html_tag_open_select(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_close_select(DilloHtml *html);\nvoid Html_tag_open_option(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_close_option(DilloHtml *html);\nvoid Html_tag_open_optgroup(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_close_optgroup(DilloHtml *html);\nvoid Html_tag_open_button(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_close_button(DilloHtml *html);\n\n#endif /* __FORM_HH__ */\n"
  },
  {
    "path": "src/gif.c",
    "content": "/*\n * File: gif.c\n *\n * Copyright (C) 1997 Raph Levien <raph@acm.org>\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * The GIF decoder for dillo. It is responsible for decoding GIF data\n * and transferring it to the dicache.\n */\n\n\n/* Notes 13 Oct 1997 --RLL\n *\n * Today, just for the hell of it, I implemented a new decoder from\n * scratch. It's oriented around pushing bytes, while the old decoder\n * was based around reads which may suspend. There were basically\n * three motivations.\n *\n * 1. To increase the speed.\n *\n * 2. To fix some bugs I had seen, most likely due to suspension.\n *\n * 3. To make sure that the code had no buffer overruns or the like.\n *\n * 4. So that the code could be released under a freer license.\n *\n * Let's see how we did on speed. I used a large image for testing\n * (fvwm95-2.gif).\n *\n * The old decoder spent a total of about 1.04 seconds decoding the\n * image. Another .58 seconds went into Image_line, almost\n * entirely conversion from colormap to RGB.\n *\n * The new decoder spent a total of 0.46 seconds decoding the image.\n * However, the time for Image_line went up to 1.01 seconds.\n * Thus, even though the decoder seems to be about twice as fast,\n * the net gain is pretty minimal. Could this be because of cache\n * effects?\n *\n * Lessons learned: The first, which I keep learning over and over, is\n * not to try to optimize too much. It doesn't work. Just keep things\n * simple.\n *\n * Second, it seems that the colormap to RGB conversion is really a\n * significant part of the overall time. It's possible that going\n * directly to 16 bits would help, but that's optimization again :)\n */\n\n\n/* TODO:\n * + Make sure to handle error cases gracefully (including aborting the\n * connection, if necessary).\n */\n\n#include <config.h>\n#ifdef ENABLE_GIF\n\n#include <stdio.h>              /* for sprintf */\n#include <string.h>             /* for memcpy and memmove */\n\n#include \"msg.h\"\n#include \"image.hh\"\n#include \"cache.h\"\n#include \"dicache.h\"\n\n#define INTERLACE      0x40\n#define LOCALCOLORMAP  0x80\n\n#define LM_to_uint(a,b)   ((((uchar_t)b)<<8)|((uchar_t)a))\n\n#define        MAXCOLORMAPSIZE         256\n#define        MAX_LWZ_BITS            12\n\n\ntypedef struct {\n   DilloImage *Image;\n   DilloUrl *url;\n   int version;\n\n   int state;\n   size_t Start_Ofs;\n   uint_t Flags;\n\n   uchar_t input_code_size;\n   uchar_t *linebuf;\n   int pass;\n\n   uint_t y;\n\n   /* state for lwz_read_byte */\n   int code_size;\n\n   /* The original GifScreen from giftopnm */\n   uint_t Width;\n   uint_t Height;\n   size_t ColorMap_ofs;\n   uint_t ColorResolution;\n   uint_t NumColors;\n   int    Background;\n   uint_t spill_line_index;\n#if 0\n   uint_t AspectRatio;    /* AspectRatio (not used) */\n#endif\n\n   /* Gif89 extensions */\n   int transparent;\n#if 0\n   /* None are used: */\n   int delayTime;\n   int inputFlag;\n   int disposal;\n#endif\n\n   /* state for the new push-oriented decoder */\n   int packet_size;       /* The amount of the data block left to process */\n   uint_t window;\n   int bits_in_window;\n   uint_t last_code;        /* Last \"compressed\" code in the look up table */\n   uint_t line_index;\n   uchar_t **spill_lines;\n   int num_spill_lines_max;\n   int length[(1 << MAX_LWZ_BITS) + 1];\n   int code_and_byte[(1 << MAX_LWZ_BITS) + 1];\n} DilloGif;\n\n/* Some invariants:\n *\n * last_code <= code_mask\n *\n * code_and_byte is stored packed: (code << 8) | byte\n */\n\n\n/*\n * Forward declarations\n */\nstatic void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize);\nstatic void Gif_close(DilloGif *gif, CacheClient_t *Client);\nstatic size_t Gif_process_bytes(DilloGif *gif, const uchar_t *buf,\n                                int bufsize, void *Buf);\n\n\n/*\n * Create a new gif structure for decoding a gif into a RGB buffer\n */\nvoid *a_Gif_new(DilloImage *Image, DilloUrl *url, int version)\n{\n   DilloGif *gif = dMalloc(sizeof(DilloGif));\n   _MSG(\"a_Gif_new: gif=%p\\n\", gif);\n\n   gif->Image = Image;\n   gif->url = url;\n   gif->version = version;\n\n   gif->Flags = 0;\n   gif->state = 0;\n   gif->Start_Ofs = 0;\n   gif->linebuf = NULL;\n   gif->Background = Image->bg_color;\n   gif->transparent = -1;\n   gif->num_spill_lines_max = 0;\n   gif->spill_lines = NULL;\n   gif->window = 0;\n   gif->packet_size = 0;\n   gif->ColorMap_ofs = 0;\n\n   return gif;\n}\n\n/*\n * Free the gif-decoding data structure.\n */\nstatic void Gif_free(DilloGif *gif)\n{\n   int i;\n\n   _MSG(\"Gif_free: gif=%p\\n\", gif);\n\n   dFree(gif->linebuf);\n   if (gif->spill_lines != NULL) {\n      for (i = 0; i < gif->num_spill_lines_max; i++)\n         dFree(gif->spill_lines[i]);\n      dFree(gif->spill_lines);\n   }\n   dFree(gif);\n}\n\n/*\n * This function is a cache client, it receives data from the cache\n * and dispatches it to the appropriate gif-processing functions\n */\nvoid a_Gif_callback(int Op, void *data)\n{\n   if (Op == CA_Send) {\n      CacheClient_t *Client = data;\n      Gif_write(Client->CbData, Client->Buf, Client->BufSize);\n   } else if (Op == CA_Close) {\n      CacheClient_t *Client = data;\n      Gif_close(Client->CbData, Client);\n   } else if (Op == CA_Abort) {\n      Gif_free(data);\n   }\n}\n\n/*\n * Receive and process new chunks of GIF image data\n */\nstatic void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize)\n{\n   uchar_t *buf;\n   int bufsize, bytes_consumed;\n\n   /* Sanity checks */\n   if (!Buf || BufSize == 0)\n      return;\n\n   buf = ((uchar_t *) Buf) + gif->Start_Ofs;\n   bufsize = BufSize - gif->Start_Ofs;\n\n   _MSG(\"Gif_write: %u bytes\\n\", BufSize);\n\n   /* Process the bytes in the input buffer. */\n   bytes_consumed = Gif_process_bytes(gif, buf, bufsize, Buf);\n\n   if (bytes_consumed < 1)\n      return;\n   gif->Start_Ofs += bytes_consumed;\n\n   _MSG(\"exit Gif_write, bufsize=%ld\\n\", (long)bufsize);\n}\n\n/*\n * Finish the decoding process (and free the memory)\n */\nstatic void Gif_close(DilloGif *gif, CacheClient_t *Client)\n{\n   _MSG(\"Gif_close: destroy gif %p\\n\", gif);\n   a_Dicache_close(gif->url, gif->version, Client);\n   Gif_free(gif);\n}\n\n\n/* --- GIF Extensions ----------------------------------------------------- */\n\n/*\n * This reads a sequence of GIF data blocks.. and ignores them!\n * Buf points to the first data block.\n *\n * Return Value\n * 0 = There wasn't enough bytes read yet to read the whole datablock\n * otherwise the size of the data blocks\n */\nstatic inline size_t Gif_data_blocks(const uchar_t *Buf, size_t BSize)\n{\n   size_t Size = 0;\n\n   if (BSize < 1)\n      return 0;\n   while (Buf[0]) {\n      if (BSize <= (size_t)(Buf[0] + 1))\n         return 0;\n      Size += Buf[0] + 1;\n      BSize -= Buf[0] + 1;\n      Buf += Buf[0] + 1;\n   }\n   return Size + 1;\n}\n\n/*\n * This is a GIF extension.  We ignore it with this routine.\n * Buffer points to just after the extension label.\n *\n * Return Value\n * 0 -- block not processed\n * otherwise the size of the extension label.\n */\nstatic inline size_t Gif_do_generic_ext(const uchar_t *Buf, size_t BSize)\n{\n\n   size_t Size = Buf[0] + 1,  /* (uchar_t + 1) can't overflow size_t */\n          DSize;\n\n   /* The Block size (the first byte) is supposed to be a specific size\n    * for each extension... we don't check.\n    */\n\n   if (Size > BSize)\n      return 0;\n   DSize = Gif_data_blocks(Buf + Size, BSize - Size);\n   if (!DSize)\n      return 0;\n   Size += DSize;\n   return Size <= BSize ? Size : 0;\n}\n\n/*\n * ?\n */\nstatic inline size_t\n Gif_do_gc_ext(DilloGif *gif, const uchar_t *Buf, size_t BSize)\n{\n   /* Graphic Control Extension */\n   size_t Size = Buf[0] + 2;\n   uint_t Flags;\n\n   if (BSize < 6 || Size > BSize)\n      return 0;\n   Buf++;\n   Flags = Buf[0];\n\n   /* The packed fields */\n#if 0\n   gif->disposal = (Buf[0] >> 2) & 0x7;\n   gif->inputFlag = (Buf[0] >> 1) & 0x1;\n\n   /* Delay time */\n   gif->delayTime = LM_to_uint(Buf[1], Buf[2]);\n#endif\n\n   /* Transparent color index, may not be valid  (unless flag is set) */\n   if ((Flags & 0x1)) {\n      gif->transparent = Buf[3];\n   }\n   return Size;\n}\n\n#define App_Ext  (0xff)\n#define Cmt_Ext  (0xfe)\n#define GC_Ext   (0xf9)\n#define Txt_Ext  (0x01)\n\n/*\n * ?\n * Return value:\n *    TRUE when the extension is over\n */\nstatic size_t Gif_do_extension(DilloGif *gif, uint_t Label,\n                               const uchar_t *buf,\n                               size_t BSize)\n{\n   switch (Label) {\n   case GC_Ext:         /* Graphics extension */\n      return Gif_do_gc_ext(gif, buf, BSize);\n\n   case Cmt_Ext:                /* Comment extension */\n      return Gif_data_blocks(buf, BSize);\n\n   case Txt_Ext:                /* Plain text Extension */\n   case App_Ext:                /* Application Extension */\n   default:\n      return Gif_do_generic_ext(buf, BSize);    /*Ignore Extension */\n   }\n}\n\n/* --- General Image Decoder ----------------------------------------------- */\n/* Here begins the new push-oriented decoder. */\n\n/*\n * ?\n */\nstatic void Gif_lwz_init(DilloGif *gif)\n{\n   gif->num_spill_lines_max = 1;\n   gif->spill_lines = dMalloc(sizeof(uchar_t *) * gif->num_spill_lines_max);\n\n   gif->spill_lines[0] = dMalloc(gif->Width);\n   gif->bits_in_window = 0;\n\n   /* First code in table = clear_code +1\n    * Last code in table = first code in table\n    * clear_code = (1<< input code size)\n    */\n   gif->last_code = (1 << gif->input_code_size) + 1;\n   memset(gif->code_and_byte, 0,\n          (1 + gif->last_code) * sizeof(gif->code_and_byte[0]));\n   gif->code_size = gif->input_code_size + 1;\n   gif->line_index = 0;\n}\n\n/*\n * Send the image line to the dicache, also handling the interlacing.\n */\nstatic void Gif_emit_line(DilloGif *gif, const uchar_t *linebuf)\n{\n   a_Dicache_write(gif->url, gif->version, linebuf, gif->y);\n   if (gif->Flags & INTERLACE) {\n      switch (gif->pass) {\n      case 0:\n      case 1:\n         gif->y += 8;\n         break;\n      case 2:\n         gif->y += 4;\n         break;\n      case 3:\n         gif->y += 2;\n         break;\n      }\n      if (gif->y >= gif->Height) {\n         gif->pass++;\n         switch (gif->pass) {\n         case 1:\n            gif->y = 4;\n            break;\n         case 2:\n            gif->y = 2;\n            break;\n         case 3:\n            gif->y = 1;\n            break;\n         default:\n            /* arriving here is an error in the input image. */\n            gif->y = 0;\n            break;\n         }\n      }\n   } else {\n      if (gif->y < gif->Height)\n         gif->y++;\n   }\n}\n\n/*\n * Decode the packetized lwz bytes\n */\nstatic void Gif_literal(DilloGif *gif, uint_t code)\n{\n   gif->linebuf[gif->line_index++] = code;\n   if (gif->line_index >= gif->Width) {\n      Gif_emit_line(gif, gif->linebuf);\n      gif->line_index = 0;\n   }\n   gif->length[gif->last_code + 1] = 2;\n   gif->code_and_byte[gif->last_code + 1] = (code << 8);\n   gif->code_and_byte[gif->last_code] |= code;\n}\n\n/*\n * ?\n */\n/* Profiling reveals over half the GIF time is spent here: */\nstatic void Gif_sequence(DilloGif *gif, uint_t code)\n{\n   uint_t o_index, o_size, orig_code;\n   uint_t sequence_length = gif->length[code];\n   uint_t line_index = gif->line_index;\n   int num_spill_lines;\n   int spill_line_index = gif->spill_line_index;\n   uchar_t *last_byte_ptr, *obuf;\n\n   gif->length[gif->last_code + 1] = sequence_length + 1;\n   gif->code_and_byte[gif->last_code + 1] = (code << 8);\n\n   /* We're going to traverse the sequence backwards. Thus,\n    * we need to allocate spill lines if the sequence won't\n    * fit entirely within the present scan line. */\n   if (line_index + sequence_length <= gif->Width) {\n      num_spill_lines = 0;\n      obuf = gif->linebuf;\n      o_index = line_index + sequence_length;\n      o_size = sequence_length - 1;\n   } else {\n      num_spill_lines = (line_index + sequence_length - 1) /\n          gif->Width;\n      o_index = (line_index + sequence_length - 1) % gif->Width + 1;\n      if (num_spill_lines > gif->num_spill_lines_max) {\n         /* Allocate more spill lines. */\n         spill_line_index = gif->num_spill_lines_max;\n         gif->num_spill_lines_max = num_spill_lines << 1;\n         gif->spill_lines = dRealloc(gif->spill_lines,\n                                      gif->num_spill_lines_max *\n                                      sizeof(uchar_t *));\n\n         for (; spill_line_index < gif->num_spill_lines_max;\n              spill_line_index++)\n            gif->spill_lines[spill_line_index] =\n                dMalloc(gif->Width);\n      }\n      spill_line_index = num_spill_lines - 1;\n      obuf = gif->spill_lines[spill_line_index];\n      o_size = o_index;\n   }\n   gif->line_index = o_index;   /* for afterwards */\n\n   /* for fixing up later if last_code == code */\n   orig_code = code;\n   last_byte_ptr = obuf + o_index - 1;\n\n   /* spill lines are allocated, and we are clear to\n    * write. This loop does not write the first byte of\n    * the sequence, however (last byte traversed). */\n   while (sequence_length > 1) {\n      sequence_length -= o_size;\n      /* Write o_size bytes to\n       * obuf[o_index - o_size..o_index). */\n      for (; o_size > 0 && o_index > 0; o_size--) {\n         uint_t code_and_byte = gif->code_and_byte[code];\n\n         _MSG(\"%d \", gif->code_and_byte[code] & 255);\n\n         obuf[--o_index] = code_and_byte & 255;\n         code = code_and_byte >> 8;\n      }\n      /* Prepare for writing to next line. */\n      if (o_index == 0) {\n         if (spill_line_index > 0) {\n            spill_line_index--;\n            obuf = gif->spill_lines[spill_line_index];\n            o_size = gif->Width;\n         } else {\n            obuf = gif->linebuf;\n            o_size = sequence_length - 1;\n         }\n         o_index = gif->Width;\n      }\n   }\n   /* Ok, now we write the first byte of the sequence. */\n   /* We are sure that the code is literal. */\n   _MSG(\"%d\", code);\n   obuf[--o_index] = code;\n   gif->code_and_byte[gif->last_code] |= code;\n\n   /* Fix up the output if the original code was last_code. */\n   if (orig_code == gif->last_code) {\n      *last_byte_ptr = code;\n      _MSG(\" fixed (%d)!\", code);\n   }\n   _MSG(\"\\n\");\n\n   /* Output any full lines. */\n   if (gif->line_index >= gif->Width) {\n      Gif_emit_line(gif, gif->linebuf);\n      gif->line_index = 0;\n   }\n   if (num_spill_lines) {\n      if (gif->line_index)\n         Gif_emit_line(gif, gif->linebuf);\n      for (spill_line_index = 0;\n           spill_line_index < num_spill_lines - (gif->line_index ? 1 : 0);\n           spill_line_index++)\n         Gif_emit_line(gif, gif->spill_lines[spill_line_index]);\n   }\n   if (num_spill_lines) {\n      /* Swap the last spill line with the gif line, using\n       * linebuf as the swap temporary. */\n      uchar_t *linebuf = gif->spill_lines[num_spill_lines - 1];\n\n      gif->spill_lines[num_spill_lines - 1] = gif->linebuf;\n      gif->linebuf = linebuf;\n   }\n   gif->spill_line_index = spill_line_index;\n}\n\n/*\n * ?\n *\n * Return Value:\n *   2 -- quit\n *   1 -- new last code needs to be done\n *   0 -- okay, but reset the code table\n *   < 0 on error\n *   -1 if the decompression code was not in the lookup table\n */\nstatic int Gif_process_code(DilloGif *gif, uint_t code, uint_t clear_code)\n{\n\n   /* A short table describing what to do with the code:\n    * code < clear_code  : This is uncompressed, raw data\n    * code== clear_code  : Reset the decompression table\n    * code== clear_code+1: End of data stream\n    * code > clear_code+1: Compressed code; look up in table\n    */\n   if (code < clear_code) {\n      /* a literal code. */\n      _MSG(\"literal\\n\");\n      Gif_literal(gif, code);\n      return 1;\n   } else if (code >= clear_code + 2) {\n      /* a sequence code. */\n      if (code > gif->last_code)\n         return -1;\n      Gif_sequence(gif, code);\n      return 1;\n   } else if (code == clear_code) {\n      /* clear code. Resets the whole table */\n      _MSG(\"clear\\n\");\n      return 0;\n   } else {\n      /* end code. */\n      _MSG(\"end\\n\");\n      return 2;\n   }\n}\n\n/*\n * ?\n */\nstatic int Gif_decode(DilloGif *gif, const uchar_t *buf, size_t bsize)\n{\n   /*\n    * Data block processing.  The image stuff is a series of data blocks.\n    * Each data block is 1 to 256 bytes long.  The first byte is the length\n    * of the data block.  0 == the last data block.\n    */\n   size_t bufsize, packet_size;\n   uint_t clear_code;\n   uint_t window;\n   int bits_in_window;\n   uint_t code;\n   int code_size;\n   uint_t code_mask;\n\n   bufsize = bsize;\n\n   /* Want to get all inner loop state into local variables. */\n   packet_size = gif->packet_size;\n   window = gif->window;\n   bits_in_window = gif->bits_in_window;\n   code_size = gif->code_size;\n   code_mask = (1 << code_size) - 1;\n   clear_code = 1 << gif->input_code_size;\n\n   /* If packet size == 0, we are at the start of a data block.\n    * The first byte of the data block indicates how big it is (0 == last\n    * datablock)\n    * packet size is set to this size; it indicates how much of the data block\n    * we have left to process.\n    */\n   while (bufsize > 0) {\n      /* lwz_bytes is the number of remaining lwz bytes in the packet. */\n      int lwz_bytes = MIN(packet_size, bufsize);\n\n      bufsize -= lwz_bytes;\n      packet_size -= lwz_bytes;\n      for (; lwz_bytes > 0; lwz_bytes--) {\n         /* printf (\"%d \", *buf) would print the depacketized lwz stream. */\n\n         /* Push the byte onto the \"end\" of the window (MSB).  The low order\n          * bits always come first in the LZW stream. */\n         window = (window >> 8) | (*buf++ << 24);\n         bits_in_window += 8;\n\n         while (bits_in_window >= code_size) {\n            /* Extract the code.  The code is code_size (3 to 12) bits long,\n             * at the start of the window */\n            code = (window >> (32 - bits_in_window)) & code_mask;\n\n            _MSG(\"code = %d, \", code);\n\n            bits_in_window -= code_size;\n            switch (Gif_process_code(gif, code, clear_code)) {\n            case 1:             /* Increment last code */\n               gif->last_code++;\n               /*gif->code_and_byte[gif->last_code+1]=0; */\n\n               if ((gif->last_code & code_mask) == 0) {\n                  if (gif->last_code == (1 << MAX_LWZ_BITS))\n                     gif->last_code--;\n                  else {\n                     code_size++;\n                     code_mask = (1 << code_size) - 1;\n                  }\n               }\n               break;\n\n            case 0:         /* Reset codes size and mask */\n               gif->last_code = clear_code + 1;\n               code_size = gif->input_code_size + 1;\n               code_mask = (1 << code_size) - 1;\n               break;\n\n            case 2:         /* End code... consume remaining data chunks..? */\n               goto error;  /* Could clean up better? */\n            default:\n               MSG(\"Gif_decode: error!\\n\");\n               goto error;\n            }\n         }\n      }\n\n      /* We reach here if\n       * a) We have reached the end of the data block;\n       * b) we ran out of data before reaching the end of the data block\n       */\n      if (bufsize <= 0)\n         break;                 /* We are out of data; */\n\n      /* Start of new data block */\n      bufsize--;\n      if (!(packet_size = *buf++)) {\n         /* This is the \"block terminator\" -- the last data block */\n         gif->state = 999;     /* BUG: should Go back to getting GIF blocks. */\n         break;\n      }\n   }\n\n   gif->packet_size = packet_size;\n   gif->window = window;\n   gif->bits_in_window = bits_in_window;\n   gif->code_size = code_size;\n   return bsize - bufsize;\n\n error:\n   gif->state = 999;\n   return bsize - bufsize;\n}\n\n/*\n * ?\n */\nstatic int Gif_check_sig(DilloGif *gif, const uchar_t *ibuf, int ibsize)\n{\n   /* at beginning of file - read magic number */\n   if (ibsize < 6)\n      return 0;\n   if (memcmp(ibuf, \"GIF87a\", 6) != 0 &&\n       memcmp(ibuf, \"GIF89a\", 6) != 0) {\n      MSG_WARN(\"\\\"%s\\\" is not a GIF file.\\n\", URL_STR(gif->url));\n      gif->state = 999;\n      return 6;\n   }\n   gif->state = 1;\n   return 6;\n}\n\n/* Read the color map\n *\n * Implements, from the spec:\n * Global Color Table\n * Local Color Table\n */\nstatic inline size_t\n Gif_do_color_table(DilloGif *gif, void *Buf,\n                    const uchar_t *buf, size_t bsize, size_t CT_Size)\n{\n   size_t Size = 3 * (1 << (1 + CT_Size));\n\n   if (Size > bsize)\n      return 0;\n\n   gif->ColorMap_ofs = (ulong_t) buf - (ulong_t) Buf;\n   gif->NumColors = (1 << (1 + CT_Size));\n   return Size;\n}\n\n/*\n * This implements, from the spec:\n * <Logical Screen> ::= Logical Screen Descriptor [Global Color Table]\n */\nstatic size_t Gif_get_descriptor(DilloGif *gif, void *Buf,\n                                 const uchar_t *buf, int bsize)\n{\n\n   /* screen descriptor */\n   size_t Size = 7,           /* Size of descriptor */\n          mysize;             /* Size of color table */\n   uchar_t Flags;\n\n   if (bsize < 7)\n      return 0;\n   Flags = buf[4];\n\n   if (Flags & LOCALCOLORMAP) {\n      mysize = Gif_do_color_table(\n                  gif, Buf, buf + 7, (size_t)bsize - 7, Flags & (size_t)0x7);\n      if (!mysize)\n         return 0;\n      Size += mysize;           /* Size of the color table that follows */\n   /*   gif->Background = buf[5]; */\n   }\n   /*   gif->Width = LM_to_uint(buf[0], buf[1]);\n        gif->Height = LM_to_uint(buf[2], buf[3]); */\n   gif->ColorResolution = (((buf[4] & 0x70) >> 3) + 1);\n   /*   gif->AspectRatio     = buf[6]; */\n\n   return Size;\n}\n\n/*\n * This implements, from the spec:\n * <Table-Based Image> ::= Image Descriptor [Local Color Table] Image Data\n *\n * ('Buf' points to just after the Image separator)\n * we should probably just check that the local stuff is consistent\n * with the stuff at the header. For now, we punt...\n */\nstatic size_t Gif_do_img_desc(DilloGif *gif, void *Buf,\n                              const uchar_t *buf, size_t bsize)\n{\n   uchar_t Flags;\n   size_t Size = 9 + 1; /* image descriptor size + first byte of image data */\n\n   if (bsize < 10)\n      return 0;\n\n   gif->Width  = LM_to_uint(buf[4], buf[5]);\n   gif->Height = LM_to_uint(buf[6], buf[7]);\n\n   /* check max image size */\n   if (gif->Width <= 0 || gif->Height <= 0 ||\n       gif->Width > IMAGE_MAX_AREA / gif->Height) {\n      MSG(\"Gif_do_img_desc: suspicious image size request %u x %u\\n\",\n          gif->Width, gif->Height);\n      gif->state = 999;\n      return 0;\n   }\n\n   /** \\todo Gamma for GIF? */\n   a_Dicache_set_parms(gif->url, gif->version, gif->Image,\n                       gif->Width, gif->Height, DILLO_IMG_TYPE_INDEXED,\n                       1 / 2.2);\n   gif->Image = NULL; /* safeguard: hereafter it may be freed by its owner */\n\n   Flags = buf[8];\n\n   gif->Flags |= Flags & INTERLACE;\n   gif->pass = 0;\n   bsize -= 9;\n   buf += 9;\n   if (Flags & LOCALCOLORMAP) {\n      size_t LSize = Gif_do_color_table(\n                        gif, Buf, buf, bsize, Flags & (size_t)0x7);\n\n      if (!LSize)\n         return 0;\n      Size += LSize;\n      buf += LSize;\n      bsize -= LSize;\n   }\n   /* Finally, get the first byte of the LZW image data */\n   if (bsize < 1)\n      return 0;\n   gif->input_code_size = *buf++;\n   if (gif->input_code_size > 8) {\n      gif->state = 999;\n      return Size;\n   }\n   gif->y = 0;\n   Gif_lwz_init(gif);\n   gif->spill_line_index = 0;\n   gif->linebuf = dMalloc(gif->Width);\n   gif->state = 3;              /*Process the lzw data next */\n   if (gif->ColorMap_ofs) {\n      a_Dicache_set_cmap(gif->url, gif->version, gif->Background,\n                         (uchar_t *) Buf + gif->ColorMap_ofs,\n                         gif->NumColors, 256, gif->transparent);\n   }\n   return Size;\n}\n\n/* --- Top level data block processors ------------------------------------ */\n#define Img_Desc (0x2c)\n#define Trailer  (0x3B)\n#define Ext_Id   (0x21)\n\n/*\n * This identifies which kind of GIF blocks are next, and processes them.\n * It returns if there isn't enough data to process the next blocks, or if\n * the next block is the lzw data (which is streamed differently)\n *\n * This implements, from the spec, <Data>* Trailer\n * <Data> ::= <Graphic Block> | <Special-Purpose Block>\n * <Special-Purpose Block> ::= Application Extension | Comment Extension\n * <Graphic Block> ::= [Graphic Control Extension] <Graphic-Rendering Block>\n * <Graphic-Rendering Block> ::= <Table-Based Image> | Plain Text Extension\n *\n * <Data>* --> GIF_Block\n * <Data>  --> while (...)\n * <Special-Purpose Block> --> Gif_do_extension\n * Graphic Control Extension --> Gif_do_extension\n * Plain Text Extension --> Gif_do_extension\n * <Table-Based Image> --> Gif_do_img_desc\n *\n * Return Value\n * 0 if not enough data is present, otherwise the number of bytes\n * \"consumed\"\n */\nstatic size_t GIF_Block(DilloGif * gif, void *Buf,\n                        const uchar_t *buf, size_t bsize)\n{\n   size_t Size = 0, mysize;\n   uchar_t C;\n\n   if (bsize < 1)\n      return 0;\n   while (gif->state == 2) {\n      if (bsize < 1)\n         return Size;\n      bsize--;\n      switch (*buf++) {\n      case Ext_Id:\n         /* get the extension type */\n         if (bsize < 2)\n            return Size;\n\n         /* Have the extension block intepreted. */\n         C = *buf++;\n         bsize--;\n         mysize = Gif_do_extension(gif, C, buf, bsize);\n\n         if (!mysize)\n            /* Not all of the extension is there.. quit until more data\n             * arrives */\n            return Size;\n\n         bsize -= mysize;\n         buf += mysize;\n\n         /* Increment the amount consumed by the extension introducer\n          * and id, and extension block size */\n         Size += mysize + 2;\n         /* Do more GIF Blocks */\n         continue;\n\n      case Img_Desc:            /* Image descriptor */\n         mysize = Gif_do_img_desc(gif, Buf, buf, bsize);\n         if (!mysize)\n            return Size;\n\n         /* Increment the amount consumed by the Image Separator and the\n          * Resultant blocks */\n         Size += 1 + mysize;\n         return Size;\n\n      case Trailer:\n         gif->state = 999;      /* BUG: should close the rest of the file */\n         return Size + 1;\n         break;                 /* GIF terminator */\n\n      default:                  /* Unknown */\n         /* gripe and complain */\n         MSG (\"gif.c::GIF_Block: Error, 0x%x found\\n\", *(buf-1));\n         gif->state = 999;\n         return Size + 1;\n      }\n   }\n   return Size;\n}\n\n\n/*\n * Process some bytes from the input gif stream. It's a state machine.\n *\n * From the GIF spec:\n * <GIF Data Stream> ::= Header <Logical Screen> <Data>* Trailer\n *\n * <GIF Data Stream> --> Gif_process_bytes\n * Header            --> State 0\n * <Logical Screen>  --> State 1\n * <Data>*           --> State 2\n * Trailer           --> State > 3\n *\n * State == 3 is special... this is inside of <Data> but all of the stuff in\n * there has been gotten and set up.  So we stream it outside.\n */\nstatic size_t Gif_process_bytes(DilloGif *gif, const uchar_t *ibuf,\n                                int bufsize, void *Buf)\n{\n   int tmp_bufsize = bufsize;\n   size_t mysize;\n\n   switch (gif->state) {\n   case 0:\n      mysize = Gif_check_sig(gif, ibuf, tmp_bufsize);\n      if (!mysize)\n         break;\n      tmp_bufsize -= mysize;\n      ibuf += mysize;\n      if (gif->state != 1)\n         break;\n\n   case 1:\n      mysize = Gif_get_descriptor(gif, Buf, ibuf, tmp_bufsize);\n      if (!mysize)\n         break;\n      tmp_bufsize -= mysize;\n      ibuf += mysize;\n      gif->state = 2;\n\n   case 2:\n      /* Ok, this loop construction looks weird.  It implements the <Data>* of\n       * the GIF grammar.  All sorts of stuff is allocated to set up for the\n       * decode part (state ==2) and then there is the actual decode part (3)\n       */\n      mysize = GIF_Block(gif, Buf, ibuf, (size_t)tmp_bufsize);\n      if (!mysize)\n         break;\n      tmp_bufsize -= mysize;\n      ibuf += mysize;\n      if (gif->state != 3)\n         break;\n\n   case 3:\n      /* get an image byte */\n      /* The users sees all of this stuff */\n      mysize = Gif_decode(gif, ibuf, (size_t)tmp_bufsize);\n      if (mysize == 0)\n         break;\n      ibuf += mysize;\n      tmp_bufsize -= mysize;\n\n   default:\n      /* error - just consume all input */\n      tmp_bufsize = 0;\n      break;\n   }\n\n   _MSG(\"Gif_process_bytes: final state %d, %ld bytes consumed\\n\",\n        gif->state, (long)(bufsize - tmp_bufsize));\n\n   return bufsize - tmp_bufsize;\n}\n\n#else /* ENABLE_GIF */\n\nvoid *a_Gif_new() { return 0; }\nvoid a_Gif_callback() { return; }\n\n#endif /* ENABLE_GIF */\n"
  },
  {
    "path": "src/history.c",
    "content": "/*\n * File: history.c\n *\n * Copyright (C) 2001-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Linear history (it also provides indexes for the navigation stack)\n */\n\n#include \"msg.h\"\n#include \"list.h\"\n#include \"history.h\"\n\n\ntypedef struct {\n   DilloUrl *url;\n   char *title;\n} H_Item;\n\n\n/* Global history list */\nstatic H_Item *history = NULL;\nstatic int history_size = 0;        /* [1 based] */\nstatic int history_size_max = 16;\n\n\n/*\n * Debug procedure.\n */\nvoid History_show()\n{\n   int i;\n\n   MSG(\"  {\");\n   for (i = 0; i < history_size; ++i)\n      MSG(\" %s\", URL_STR(history[i].url));\n   MSG(\" }\\n\");\n}\n\n/*\n * Add a new H_Item at the end of the history list\n * (taking care of not making a duplicate entry)\n */\nint a_History_add_url(DilloUrl *url)\n{\n   int i, idx;\n\n   _MSG(\"a_History_add_url: '%s' \", URL_STR(url));\n   for (i = 0; i < history_size; ++i)\n      if (!a_Url_cmp(history[i].url, url) &&\n          !strcmp(URL_FRAGMENT(history[i].url), URL_FRAGMENT(url)))\n         break;\n\n   if (i < history_size) {\n      idx = i;\n      _MSG(\"FOUND at idx=%d\\n\", idx);\n   } else {\n      idx = history_size;\n      a_List_add(history, history_size, history_size_max);\n      history[idx].url = a_Url_dup(url);\n      history[idx].title = NULL;\n      ++history_size;\n      _MSG(\"ADDED at idx=%d\\n\", idx);\n   }\n\n   /* History_show(); */\n\n   return idx;\n}\n\n/*\n * Return the DilloUrl field (by index)\n */\nconst DilloUrl *a_History_get_url(int idx)\n{\n   _MSG(\"a_History_get_url: \");\n   /* History_show(); */\n\n   dReturn_val_if_fail(idx >= 0 && idx < history_size, NULL);\n\n   return history[idx].url;\n}\n\n/*\n * Return the title field (by index)\n * ('force' returns URL_STR when there's no title)\n */\nconst char *a_History_get_title(int idx, int force)\n{\n   dReturn_val_if_fail(idx >= 0 && idx < history_size, NULL);\n\n   if (history[idx].title)\n      return history[idx].title;\n   else if (force)\n      return URL_STR(history[idx].url);\n   else\n      return NULL;\n}\n\n/*\n * Return the title field (by url)\n * ('force' returns URL_STR when there's no title)\n */\nconst char *a_History_get_title_by_url(const DilloUrl *url, int force)\n{\n   int i;\n\n   dReturn_val_if_fail(url != NULL, NULL);\n\n   for (i = 0; i < history_size; ++i)\n      if (a_Url_cmp(url, history[i].url) == 0)\n         break;\n\n   if (i < history_size && history[i].title)\n      return history[i].title;\n   else if (force)\n      return URL_STR_(url);\n   return NULL;\n}\n\n/*\n * Set the page-title for a given URL\n */\nvoid a_History_set_title_by_url(const DilloUrl *url, const char *title)\n{\n   int i;\n\n   dReturn_if (url == NULL);\n\n   for (i = history_size - 1; i >= 0; --i)\n      if (a_Url_cmp(url, history[i].url) == 0)\n         break;\n\n   if (i >= 0) {\n      dFree(history[i].title);\n      history[i].title = dStrdup(title);\n   } else {\n      MSG_ERR(\"a_History_set_title_by_url: %s not found\\n\", URL_STR(url));\n   }\n}\n\n\n/*\n * Free all the memory used by this module\n */\nvoid a_History_freeall()\n{\n   int i;\n\n   for (i = 0; i < history_size; ++i) {\n      a_Url_free(history[i].url);\n      dFree(history[i].title);\n   }\n   dFree(history);\n}\n"
  },
  {
    "path": "src/history.h",
    "content": "\n#ifndef __DILLO_HISTORY_H__\n#define __DILLO_HISTORY_H__\n\n#include \"url.h\"\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nint a_History_add_url(DilloUrl *url);\nvoid a_History_set_title_by_url(const DilloUrl *url, const char *title);\nconst DilloUrl *a_History_get_url(int idx);\nconst char *a_History_get_title(int idx, int force);\nconst char *a_History_get_title_by_url(const DilloUrl *url, int force);\nvoid a_History_freeall(void);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __DILLO_HISTORY_H__ */\n"
  },
  {
    "path": "src/hsts.c",
    "content": "/*\n * File: hsts.c\n * HTTP Strict Transport Security\n *\n * Copyright 2015 corvid\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* To preload hosts, as of 2015, chromium is the list keeper:\n * https://src.chromium.org/viewvc/chrome/trunk/src/net/http/transport_security_state_static.json\n * although mozilla's is easier to work from (and they trim it based on\n * criteria such as max-age must be at least some number of months)\n * https://mxr.mozilla.org/mozilla-central/source/security/manager/ssl/nsSTSPreloadList.inc?raw=1\n */\n\n#include <time.h>\n#include <errno.h>\n#include <limits.h> /* for INT_MAX */\n#include <ctype.h> /* for isspace */\n#include <stdlib.h> /* for strtol */\n\n#include \"hsts.h\"\n#include \"msg.h\"\n#include \"../dlib/dlib.h\"\n#include \"IO/tls.h\"\n\ntypedef struct {\n   char *host;\n   time_t expires_at;\n   bool_t subdomains;\n} HstsData_t;\n\n/* When there is difficulty in representing future dates, use the (by far)\n * most likely latest representable time of January 19, 2038.\n */\nstatic time_t hsts_latest_representable_time;\nstatic Dlist *domains;\n\nstatic void Hsts_free_policy(HstsData_t *p)\n{\n   dFree(p->host);\n   dFree(p);\n}\n\nvoid a_Hsts_freeall()\n{\n   if (prefs.http_strict_transport_security) {\n      HstsData_t *policy;\n      int i, n = dList_length(domains);\n\n      for (i = 0; i < n; i++) {\n         policy = dList_nth_data(domains, i);\n         Hsts_free_policy(policy);\n      }\n      dList_free(domains);\n   }\n}\n\n/*\n * Compare function for searching a domain node by domain string\n */\nstatic int Domain_node_domain_str_cmp(const void *v1, const void *v2)\n{\n   const HstsData_t *node = v1;\n   const char *host = v2;\n\n   return dStrAsciiCasecmp(node->host, host);\n}\n\nstatic HstsData_t *Hsts_get_policy(const char *host)\n{\n   return dList_find_sorted(domains, host, Domain_node_domain_str_cmp);\n}\n\nstatic void Hsts_remove_policy(HstsData_t *policy)\n{\n   if (policy) {\n      _MSG(\"HSTS: removed policy for %s\\n\", policy->host);\n      Hsts_free_policy(policy);\n      dList_remove(domains, policy);\n   }\n}\n\n/*\n * Return the time_t for a future time.\n */\nstatic time_t Hsts_future_time(long seconds_from_now)\n{\n   time_t ret, now = time(NULL);\n   struct tm *tm = gmtime(&now);\n\n   if (seconds_from_now > INT_MAX - tm->tm_sec)\n      tm->tm_sec = INT_MAX;\n   else\n      tm->tm_sec += seconds_from_now;\n\n   ret = mktime(tm);\n   if (ret == (time_t) -1)\n      ret = hsts_latest_representable_time;\n\n   return ret;\n}\n\n/*\n * Compare function for searching domains.\n */\nstatic int Domain_node_cmp(const void *v1, const void *v2)\n{\n   const HstsData_t *node1 = v1, *node2 = v2;\n\n   return dStrAsciiCasecmp(node1->host, node2->host);\n}\n\nstatic void Hsts_set_policy(const char *host, long max_age, bool_t subdomains)\n{\n   time_t exp = Hsts_future_time(max_age);\n   HstsData_t *policy = Hsts_get_policy(host);\n\n   _MSG(\"HSTS: %s %s%s: until %s\", (policy ? \"modify\" : \"add\"), host,\n       (subdomains ? \" and subdomains\" : \"\"), ctime(&exp));\n\n   if (policy == NULL) {\n      policy = dNew0(HstsData_t, 1);\n      policy->host = dStrdup(host);\n      dList_insert_sorted(domains, policy, Domain_node_cmp);\n   }\n   policy->subdomains = subdomains;\n   policy->expires_at = exp;\n}\n\n/*\n * Read the next attribute.\n */\nstatic char *Hsts_parse_attr(const char **header_str)\n{\n   const char *str;\n   uint_t len;\n\n   while (dIsspace(**header_str))\n      (*header_str)++;\n\n   str = *header_str;\n   /* find '=' at end of attr, ';' after attr/val pair, '\\0' end of string */\n   len = strcspn(str, \"=;\");\n   *header_str += len;\n\n   while (len && (str[len - 1] == ' ' || str[len - 1] == '\\t'))\n      len--;\n   return dStrndup(str, len);\n}\n\n/*\n * Get the value in *header_str.\n */\nstatic char *Hsts_parse_value(const char **header_str)\n{\n   uint_t len;\n   const char *str;\n\n   if (**header_str == '=') {\n      (*header_str)++;\n      while (dIsspace(**header_str))\n         (*header_str)++;\n\n      str = *header_str;\n      /* finds ';' after attr/val pair or '\\0' at end of string */\n      len = strcspn(str, \";\");\n      *header_str += len;\n\n      while (len && (str[len - 1] == ' ' || str[len - 1] == '\\t'))\n         len--;\n   } else {\n      str = *header_str;\n      len = 0;\n   }\n   return dStrndup(str, len);\n}\n\n/*\n * Advance past any value.\n */\nstatic void Hsts_eat_value(const char **str)\n{\n   if (**str == '=')\n      *str += strcspn(*str, \";\");\n}\n\n/*\n * The reponse for this url had an HSTS header, so let's take action.\n */\nvoid a_Hsts_set(const char *header, const DilloUrl *url)\n{\n   long max_age;\n   const char *host = URL_HOST(url);\n   bool_t max_age_valid = FALSE, subdomains = FALSE;\n\n   _MSG(\"HSTS header for %s: %s\\n\", host, header);\n\n   if (!a_Tls_certificate_is_clean(url)) {\n      /* RFC 6797 gives rationale in section 14.3. */\n      _MSG(\"But there were certificate warnings, so ignore it (!)\\n\");\n      return;\n   }\n\n   /* Iterate until there is nothing left of the string */\n   while (*header) {\n      char *attr;\n      char *value;\n\n      /* Get attribute */\n      attr = Hsts_parse_attr(&header);\n\n      /* Get the value for the attribute and store it */\n      if (dStrAsciiCasecmp(attr, \"max-age\") == 0) {\n         value = Hsts_parse_value(&header);\n         if (isdigit(*value)) {\n            errno = 0;\n            max_age = strtol(value, NULL, 10);\n            if (errno == ERANGE)\n               max_age = INT_MAX;\n            max_age_valid = TRUE;\n         }\n         dFree(value);\n      } else if (dStrAsciiCasecmp(attr, \"includeSubDomains\") == 0) {\n         subdomains = TRUE;\n         Hsts_eat_value(&header);\n      } else if (dStrAsciiCasecmp(attr, \"preload\") == 0) {\n         /* 'preload' is not part of the RFC, but what does google care for\n          * standards? They require that 'preload' be specified by a domain\n          * in order to be added to their preload list.\n          */\n      } else {\n         MSG(\"HSTS: header contains unknown attribute: '%s'\\n\", attr);\n         Hsts_eat_value(&header);\n      }\n\n      dFree(attr);\n\n      if (*header == ';')\n         header++;\n   }\n   if (max_age_valid) {\n      if (max_age > 0)\n         Hsts_set_policy(host, max_age, subdomains);\n      else\n         Hsts_remove_policy(Hsts_get_policy(host));\n   }\n}\n\nstatic bool_t Hsts_expired(HstsData_t *policy)\n{\n   time_t now = time(NULL);\n   bool_t ret = (now > policy->expires_at) ? TRUE : FALSE;\n\n   if (ret) {\n      _MSG(\"HSTS: expired\\n\");\n   }\n   return ret;\n}\n\nbool_t a_Hsts_require_https(const char *host)\n{\n   bool_t ret = FALSE;\n\n   if (host) {\n      HstsData_t *policy = Hsts_get_policy(host);\n\n      if (policy) {\n         _MSG(\"HSTS: matched host %s\\n\", host);\n         if (Hsts_expired(policy))\n            Hsts_remove_policy(policy);\n         else\n            ret = TRUE;\n      }\n      if (!ret) {\n         const char *domain_str;\n\n         for (domain_str = strchr(host+1, '.');\n              domain_str != NULL && *domain_str;\n              domain_str = strchr(domain_str+1, '.')) {\n            policy = Hsts_get_policy(domain_str+1);\n\n            if (policy && policy->subdomains) {\n               _MSG(\"HSTS: matched %s under %s subdomain rule\\n\", host,\n                   policy->host);\n               if (Hsts_expired(policy)) {\n                  Hsts_remove_policy(policy);\n               } else {\n                  ret = TRUE;\n                  break;\n               }\n            }\n         }\n      }\n   }\n   return ret;\n}\n\nstatic void Hsts_preload(FILE *stream)\n{\n   const int LINE_MAXLEN = 4096;\n   const long ONE_YEAR = 60 * 60 * 24 * 365;\n\n   char *rc, *subdomains;\n   char line[LINE_MAXLEN];\n   char domain[LINE_MAXLEN];\n\n   /* Get all lines in the file */\n   while (!feof(stream)) {\n      line[0] = '\\0';\n      rc = fgets(line, LINE_MAXLEN, stream);\n      if (!rc && ferror(stream)) {\n         MSG_WARN(\"HSTS: Error while reading preload entries: %s\\n\",\n                  dStrerror(errno));\n         return; /* bail out */\n      }\n\n      /* Remove leading and trailing whitespace */\n      dStrstrip(line);\n\n      if (line[0] != '\\0' && line[0] != '#') {\n         int i = 0, j = 0;\n\n         /* Get the domain */\n         while (line[i] != '\\0' && !dIsspace(line[i]))\n            domain[j++] = line[i++];\n         domain[j] = '\\0';\n\n         /* Skip past whitespace */\n         while (dIsspace(line[i]))\n            i++;\n\n         subdomains = line + i;\n\n         if (dStrAsciiCasecmp(subdomains, \"true\") == 0)\n            Hsts_set_policy(domain, ONE_YEAR, TRUE);\n         else if (dStrAsciiCasecmp(subdomains, \"false\") == 0)\n            Hsts_set_policy(domain, ONE_YEAR, FALSE);\n         else {\n            MSG_WARN(\"HSTS: format of line not recognized. Ignoring '%s'.\\n\",\n                     line);\n         }\n      }\n   }\n}\n\nvoid a_Hsts_init(FILE *preload_file)\n{\n   if (prefs.http_strict_transport_security) {\n      struct tm future_tm = {7, 14, 3, 19, 0, 138, 0, 0, 0, 0, 0};\n\n      hsts_latest_representable_time = mktime(&future_tm);\n      domains = dList_new(32);\n\n      if (preload_file) {\n         Hsts_preload(preload_file);\n         fclose(preload_file);\n      }\n   }\n}\n\n"
  },
  {
    "path": "src/hsts.h",
    "content": "#ifndef __HSTS_H__\n#define __HSTS_H__\n\n#include \"d_size.h\"\n#include \"url.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nvoid  a_Hsts_init(FILE *fp);\nvoid  a_Hsts_set(const char *header, const DilloUrl *url);\nbool_t a_Hsts_require_https(const char *host);\nvoid  a_Hsts_freeall( void );\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* !__HSTS_H__ */\n"
  },
  {
    "path": "src/hsts_preload",
    "content": "# This HTTP Strict Transport Security preload file was created on 2016-06-28\n# from the list in\n# https://hg.mozilla.org/mozilla-central/raw-file/tip/security/manager/ssl/nsSTSPreloadList.inc\n# Format: domain include_subdomains\n\n0.me.uk true\n007sascha.de true\n01electronica.com.ar true\n050media.nl true\n0513c.com true\n0au.de true\n0knowledge.de true\n0paste.com true\n0x.cx true\n0x.sk true\n0x0a.net true\n0x44.net true\n0x52.net true\n0x90.fi true\n0x90.io true\n0xa.in true\n0xee.eu true\n0xf00.ch true\n0xfc.de true\n0xn.de true\n1000minds.com true\n1001.best true\n1011100.com true\n1017scribes.com true\n1022996493.rsc.cdn77.org true\n10hz.de true\n123test.fr true\n126ium.moe true\n12vpn.org true\n1464424382.rsc.cdn77.org true\n14it.de true\n1750studios.com true\n17hats.com true\n188trafalgar.ca true\n18f.gov true\n1972969867.rsc.cdn77.org true\n1a-diamantscheiben.de true\n1a-vermessung.at true\n1a-werkstattgeraete.de true\n1co-jp.net true\n1cover.com true\n1cover.com.au true\n1hourproofreading.com true\n1km.ro true\n1p.ro true\n1q365a.com true\n1st-community.de true\n1stcapital.com.sg true\n1whw.co.uk true\n1xcess.com true\n2048game.co.uk true\n21.co.uk true\n247healthshop.com true\n24ip.com true\n24ip.de true\n24ip.fr true\n25daysof.io true\n28spots.net true\n2bas.nl true\n2bis10.de true\n2brokegirls.org true\n2carpros.com false\n2gen.com true\n2kgwf.fi true\n2nains.ch true\n2nerds1bit.com true\n2programmers.net true\n2ulcceria.nl true\n300m.com true\n301.website true\n321live.nl true\n33-km.ru true\n33drugstore.com true\n3473-wiki.de true\n35792.de true\n360gradus.com true\n365.or.jp false\n365beautyworld.com true\n365healthworld.com true\n368mibn.com true\n3do3dont.com true\n3r.org.uk true\n3s-hosting.de true\n3timegear.com true\n403.ch true\n404.sh true\n4500.co.il true\n4679.space false\n4d2.xyz true\n4eyes.ch true\n4g-server.eu false\n4loc.us true\n4mm.org true\n4project.co.il true\n4w-performers.link false\n4winds.pt true\n4x.fi true\n50plusnet.nl true\n57aromas.com true\n5apps.com true\n60ych.net true\n6120.eu true\n646.io true\n6660111.ru true\n692b8c32.de true\n6969.us true\n69square.com true\n700.az true\n7183.org true\n777coin.com true\n7kovrikov.ru true\n7sons.de true\n83i.net true\n888azino.com true\n888sport.dk true\n888sport.it true\n8ack.de true\n8t8.eu true\n8thportsmouth.org.uk true\n91tianmi.com false\n92url.com true\n960news.ca true\n9vx.org true\na2nutrition.com.au true\naa-tour.ru true\naaeblog.com true\naaeblog.org true\naanmpc.com true\naaoo.net true\naapas.org.ar true\naapp.space true\naaron-gustafson.com true\naaronkimmig.de true\naaronsilber.me true\naatf.us true\naati.info true\nabacustech.co.jp true\nabareplace.com true\nabc.li true\nabeestrada.com true\naberdeenjudo.co.uk true\nabeus.com true\nabiapp.net true\nabilitylist.org true\nabilitynet.org.uk true\nabilymp06.net true\nabiturma.de true\nabmahnhelfer.de false\nabmgood.com true\nabou.to false\nabout.ge true\naboutmyip.info true\naboutmyproperty.ca true\nabrilect.com true\nabseits.org true\nabsynthe-inquisition.fr true\nabthorpe.org true\nabtom.de true\nabury.fr true\nabury.me true\nac-town.com true\nacabadosboston.com true\naccelerole.com true\naccess-sofia.org true\naccessacademies.org true\naccounts-p.com true\naccounts.firefox.com true\naccounts.google.com true\naccuenergy.com true\nacheconcursos.com.br true\nachenar.net true\nacheritage.co.uk true\nachromatisch.de true\nacisonline.net true\naclu.org false\nacnpacific.com true\nacorns.com true\nacrylicwifi.com true\nacsemb.org true\nacslimited.co.uk true\nactivatemyiphone.com true\nactivateplay.com true\nactive.hu false\nactorsroom.com true\nactserv.co.ke true\nacuica.co.uk false\nacus.gov true\nad-notam.pt true\nadam-kostecki.de true\nadambyers.com true\nadamgold.net true\nadamkaminski.com true\nadamkostecki.de true\nadamoutler.com true\nadamradocz.com true\nadamricheimer.com true\nadams.dk true\nadams.net true\nadamstas.com true\nadaptivemechanics.edu.au true\nadastra.re true\nadayinthelifeof.nl true\nadblock.ovh true\nadblockextreme.com true\nadblockextreme.org true\naddaxpetroleum.com true\nadderall.space true\naddstar.jp true\nadduono.com true\nadec-emsa.ae true\nadelaides.com true\nadelevie.com true\naderal.io true\nadevel.eu true\nadigitali.biz true\nadimaja.com true\nadiponectinsupplement.info true\nadiponectinsupplement.net true\nadjagu.org true\nadlershop.ch true\nadme.co.il true\nadmin.fedoraproject.org true\nadmin.google.com true\nadmin.stg.fedoraproject.org true\nadmitcard.co.in true\nadoal.net true\nadopteunsiteflash.com true\nadorai.tk true\nadquisitio.co.uk true\nadquisitio.de true\nadquisitio.es true\nadquisitio.fr true\nadquisitio.in true\nadquisitio.it true\nadrenaline-gaming.ru false\nadrianajewelry.my true\nadrl.ca true\naduvi.de true\nadvanced-online.eu true\nadvancedseotool.it true\nadvancis.net true\nadvelty.cz true\nadventistdeploy.org true\nadventureforest.de false\nadver.top true\nadviespuntklokkenluiders.nl true\nadzuna.ca true\nadzuna.co.uk true\nadzuna.co.za true\nadzuna.com.au true\nadzuna.com.br true\nadzuna.de true\nadzuna.fr true\nadzuna.in true\nadzuna.nl true\nadzuna.pl true\nadzuna.ru true\naegee-utrecht.nl true\naeon.co false\naerolog.co true\naeyoun.com true\nafb24.de true\naffiliateroyale.com true\naffilie.de true\naffinitysync.com true\naffordableazdivorce.com true\naficotroceni.ro true\nafp548.com true\nafricatravel.de true\nafrodigital.uk true\nafuh.de true\nafvallendoeje.nu true\nageg.ca true\nagenda-loto.net true\nagilebits.net false\nagonswim.com true\nagowa338.de true\nagroline.by true\nagwa.name true\nahd.com true\nahero4all.org true\nahmad.works true\nahmerjamilkhan.org true\nahoyconference.com true\nahri.ovh true\nahwatukeefoothillsmontessori.com true\nahxxm.com true\naia.de true\naidanwoods.com true\naids.gov true\naie.de true\naiesecarad.ro true\naikido-linz.at true\naikido-wels.at true\nairbly.com true\nairlea.com true\nairlinecheckins.com true\nairsoft.ch true\naishnair.com true\naisle3.space true\naiticon.com true\naivd.lol true\naiwdirect.com true\najouin.com true\nakachanikuji.com true\nakaoma.com false\nakelius.de true\nakerek.hu true\nakhilindurti.com false\nakhras.at true\nakombakom.net true\nakostecki.de true\nakoww.de true\nakropolis-ravensburg.de true\nakselinurmio.fi true\nakstudentsfirst.org true\naktiv-naturheilmittel.at true\naktiv-naturheilmittel.ch true\naktiv-naturheilmittel.de true\naktivist.in true\naladdin.ie true\naladdinschools.appspot.com true\nalainwolf.ch true\nalainwolf.net true\nalair.cn true\nalaninkenya.org true\nalanlee.net true\nalaricfavier.eu true\nalariel.de true\nalasta.info true\nalastyr.com false\nalbertbogdanowicz.pl true\nalbion2.org true\naldes.co.za true\naleax.me true\nalecpap.com true\naleksib.fi true\nalela.fr true\nalertwire.com true\nalex-ross.co.uk true\nalexandra-schulze.de true\nalexandre.sh true\nalexbaker.org true\nalexei.su true\nalexgaynor.net true\nalexhaydock.co.uk true\nalexhd.de true\nalexisabarca.com true\nalexmerkel.com true\nalexmerkel.me true\nalexmerkel.xyz true\nalexn.org true\nalexsergeyev.com true\nalexsexton.com true\nalexvetter.de true\nalexwardweb.com true\nalexyang.me true\nalienstat.com true\nalittlebitcheeky.com false\nalkami.com true\nalkamitech.com true\nall.tf false\nall4os.com true\nallbenjoy.de true\nallcarepharmacy.com true\nallforyou.at true\nalliedfrozenstorage.com true\nallinnote.com true\nallmbw.com true\nalltheducks.com true\nallthethings.co.nz true\nallthingssquared.com true\nallthingswild.co.uk true\nalmeria-si.fr true\nalmeria.fr true\nalnitech.com true\nalocato.com true\nalpca.org true\nalpencam.com true\nalpencams.com true\nalpha-force.net true\nalphabuild.io true\nalphalabs.xyz true\nalphassl.de true\nalphatrash.de true\nalt-three.com true\nalt.org true\naltedirect.com true\nalterbaum.net true\naltesses.eu true\naltestore.com true\naltfire.ca true\naltonblom.com true\naltopia.com true\naluroof.eu true\nalza.cz true\nalza.de true\nalza.sk true\nalzashop.com true\nam3.se true\nama.ne.jp true\namateri.com true\namavis.org true\namazing-gaming.fr true\nambiente.one true\nambiq.nl true\namcvega.com true\namdouglas.com true\namdouglas.uk true\namees.me true\namerican-truck-simulator.de true\namericanbio.com true\namericanworkwear.nl true\namerickykongres.cz true\namerigroup.com true\namerimarkdirect.com true\namerimex.cc true\namihub.com true\namilx.com true\namilx.org true\namisharingstuff.com true\namishsecurity.com true\namitube.com true\namnesy.fr true\namoory.com false\namv-crm.ru true\nanadoluefessk.org true\nanadoluefessporkulubu.org true\nanagra.ms true\nanalytic-s.ml true\nananke.io true\nanassiriphotography.com false\nanastasiafond.com true\nand-stuff.nl true\nand.com true\nanderslind.dk true\nandisadhdspot.com true\nandre-ballensiefen.de true\nandreaboero.it true\nandreas-kluge.eu true\nandreasfeusi.ch true\nandreaskluge.eu true\nandreastoneman.com true\nandreigec.net true\nandrepicard.de true\nandrewbroekman.com true\nandrewhowden.com true\nandrewimeson.com true\nandrewmichaud.com true\nandrewmichaud.me true\nandrewsun.com true\nandrewtebert.com true\nandrewthelott.net true\nandrewx.net true\nandreypopp.com true\nandsat.org true\nanduril.de true\nanduril.eu true\nandyuk.org true\nanedot.com true\nanedot.xyz true\nanetaben.nl true\nanglictinatabor.cz true\nangrapa.ru true\nangristan.fr true\nangularjs.org true\nanimalnet.de false\nanime.my false\nanimesfusion.com.br true\nanimesharp.com true\nanitaalbersen.nl true\nanitube-nocookie.ch true\nanitube.ch true\nankarakart.com.tr true\nankaraprofesyonelwebtasarim.com true\nankarauzmanlarnakliyat.com true\nannabellaw.com true\nannahmeschluss.de true\nannarokina.com true\nannetta.com true\nannevankesteren.com true\nannevankesteren.nl true\nannevankesteren.org true\nannuaire-photographe.fr true\nanonboards.com true\nanoncom.net true\nanoneko.com true\nanonukradio.org true\nanonym-surfen.de true\nanonyme-spieler.at true\nanonymousstatecollegelulzsec.com true\nanook.com true\nanother.ch true\nansdell.info true\nansdell.net true\nantarcti.co true\nanthenor.co.uk true\nantipolygraph.org true\nantoine-roux.fr true\nantoinedeschenes.com true\nantoniomarques.eu true\nantons.io true\nanyprime.net true\nanyways.at true\nanzeiger.ag true\nao-dev.com true\nao2.it true\naojf.fr true\naopedeure.nl true\naosus.org true\napachehaus.de false\napadvantage.com true\napeasternpower.com true\naperturesciencelabs.de true\napervita.net true\napexitsolutions.ca true\napi-geek.com true\napi.cloudflare.com false\napi.intercom.io true\napi.lookout.com false\napi.simple.com false\napi.xero.com false\napis.google.com true\napmg-certified.com true\napmg-cyber.com true\napn-einstellungen.de true\napolloyl.com true\naponow.de true\naposke.com true\napp-arena.com true\napp.lookout.com false\napp.recurly.com true\napp.simpletax.ca false\napp.yinxiang.com false\nappart.ninja true\nappartementhaus-badria.de true\nappchive.net true\nappdrinks.com true\nappengine.google.com true\nappharbor.com true\napple-watch-zubehoer.de true\nappleoosa.com true\napplic8.com true\nappmobile.io true\nappointed.at true\nappreciationkards.com true\napprobo.com true\napps.facebook.com false\napps.fedoraproject.org true\napps.stg.fedoraproject.org true\nappsdash.io true\nappson.co.uk true\nappuro.com true\naprovpn.com true\naprsdroid.org true\naprz.de true\napstudynotes.org true\naquapoint.kiev.ua true\naquilaguild.com true\naramido.de true\naranycsillag.net true\narawaza.com true\narbeitskreis-asyl-eningen.de true\narbitrary.ch true\narboworks.com true\narchlinux.de true\narctic.gov true\nardtrade.ru true\nareafiftylan.nl true\nareatrend.com true\narendburgers.nl true\nargh.io true\narguggi.co.uk true\narima.co.ke true\naristocrates.co true\narivo.com.br false\narjandejong.eu true\narksan.com.tr true\narlen.io true\narmingrodon.de true\narmor.com true\narnaudfeld.de true\narnesolutions.com true\narnor.org true\naroonchande.com true\narpa.ph true\narrive.by true\narrmaforum.com true\narrowgrove.com true\nars.toscana.it true\nartegusto.ru true\narteseideias.com.pt true\nartetrama.com true\narticaexports.com true\nartifex21.com true\nartifex21.fr true\nartisanhd.com true\nartistnetwork.nl true\nartmoney.com true\nartofwhere.com true\nartspac.es true\narty.name true\narubasunsetbeach.com true\narvamus.eu true\narvid.io true\narw.me true\naryasenna.net true\narzaroth.com false\narzid.com true\nas.se true\nas9178.net true\nasandu.eu true\nascamso.com true\nascii.moe true\nasciitable.tips true\naserver.co true\nashutoshmishra.org true\nasianodor.com true\nask.fedoraproject.org true\nask.stg.fedoraproject.org true\naskfit.cz true\naskmagicconch.com true\naskwhy.cz true\naskwhy.eu true\nasm-x.com true\nasmui.ml true\naspargesgaarden.no true\naspires.co.jp true\nasr.li true\nasr.rocks true\nass.org.au true\nassekuranzjobs.de true\nastengox.com true\nastrolpost.com true\nastromelody.com true\nasun.co true\nasurepay.cc true\nat.search.yahoo.com false\natavio.de true\natbeckett.com true\natc.io true\natchleyjazz.com true\natchleyjazz.org true\natchleylab.org true\nateli.com true\natg.soy true\natgseed.co.uk true\natgseed.uk true\nath0.org true\nathenelive.com true\nathensbusinessresources.us true\nathul.xyz true\natishchenko.com true\natisoft.biz true\natisoft.com.tr true\natisoft.net true\natisoft.net.tr true\natisoft.web.tr true\natlantichomes.com.au true\natlantischild.hu true\natlassian.net true\natletika.hu true\natnis.com true\natolm.net true\natop.io true\natrinik.org true\natte.fi true\nattorney.org.il true\naubiosales.com true\naucubin.moe false\naudiovisualdevices.com.au true\naudisto.com true\nauditmatrix.com true\nauf-feindgebiet.de true\naufmerksamkeitsstudie.com true\naugaware.org true\naugias.org true\naugiero.it true\naugustian-life.cz true\naugustiner-kantorei-erfurt.de true\naugustiner-kantorei.de true\naukaraoke.su true\naulo.in false\naunali1.com true\naurainfosec.com.au true\nausschreibungen-suedtirol.it true\naussiecable.org true\naussiehq.com.au true\nauth.adult true\nauthint.com true\nauthoritynutrition.com true\nauto-anleitung.de true\nauto-serwis.zgorzelec.pl true\nautodeploy.it true\nautojuhos.sk true\nautokovrik-diskont.ru true\nautoledky.sk true\nautomacity.com true\nautotsum.com true\nautumnwindsagility.com true\nava-creative.de true\navacariu.me true\navalon-island.ru true\navantmfg.com true\navastantivirus.ro true\navenueeyecare.com true\navg.club true\naviacao.pt true\navinet.com false\navmemo.com true\navmo.pw true\navmoo.com true\navso.pw true\navsox.com true\navtovokzaly.ru true\navxo.pw true\nawanderlustadventure.com true\naww.moe true\nawxg.com true\naxka.com false\naxrec.de true\naylak.com true\nayurveda101.com true\nazabani.com true\nazazy.net true\nazimut.fr true\nazino777.ru true\nazirevpn.com true\naztrix.me true\nazzag.co.uk true\nazzorti.com true\nb-root-force.de true\nb2and.com false\nb303.me true\nb64.club true\nbaalsworld.de true\nbabacasino.net true\nbabarkata.com true\nbabelfisch.eu true\nbaby-click.de true\nbabybee.ie true\nbabyfotograf-schweiz.ch true\nbabysaying.me true\nbabystep.tv true\nbacchanallia.com true\nbackeby.eu true\nbackmountaingas.com true\nbackscattering.de true\nbackschues.net true\nbacontreeconsulting.com true\nbacula.jp true\nbad.horse true\nbadbee.cc true\nbadges.fedoraproject.org true\nbadges.stg.fedoraproject.org true\nbadhusky.com true\nbadkamergigant.com true\nbadlink.org true\nbadoo.com true\nbaer.im true\nbaer.one true\nbaffinlee.com true\nbagelsbakery.com false\nbah.im false\nbaiker.info true\nbaiyangliu.com true\nbajic.ch true\nbakabt.info true\nbakaweb.fr true\nbalboa.io true\nbalikonos.cz true\nbaliyano.com true\nbalkonien.org true\nball.holdings true\nballmerpeak.org true\nballotapi.com true\nbananabandy.com true\nbananium.fr true\nbancoctt.pt true\nbandb.xyz true\nbandrcrafts.com true\nbank.simple.com false\nbankcardoffer.com true\nbankersonline.com true\nbankin.com true\nbankofdenton.com true\nbanqingdiao.com true\nbaofengtech.com true\nbarbate.fr true\nbarbu.family true\nbarcodeberlin.com true\nbarcoderealty.com true\nbardiharborow.com true\nbarisi.me true\nbarqo.co true\nbarrelhead.org true\nbarrett.ag true\nbarrut.me false\nbarslecht.com true\nbarslecht.nl true\nbartel.ws true\nbartlamboo.nl true\nbaruch.me true\nbarunisystems.com true\nbashc.at true\nbashcode.ninja true\nbaskettemple.com true\nbasnieuwenhuizen.nl true\nbasnoslovno.com.ua true\nbasnoslovno.ru true\nbassh.net false\nbastianstalder.ch true\nbaud.ninja true\nbautied.de true\nbayden.com true\nbayrisch-fuer-anfaenger.de true\nbazdell.com true\nbbdos.ru true\nbblovess.cn true\nbbnx.net true\nbbuio.com true\nbc-bd.org true\nbcbsmagentprofile.com true\nbcchack.com true\nbcdonadio.com true\nbcheng.cf true\nbcmlu.org true\nbcrook.com false\nbcswampcabins.com true\nbcsytv.com true\nbcweightlifting.ca true\nbdikaros-network.net true\nbeaglewatch.com true\nbeamitapp.com true\nbeanjuice.me true\nbeans-one.com false\nbeardydave.com true\nbeastowner.li true\nbeautykat.ru true\nbebef.de true\nbedabox.com true\nbeeksnetwork.nl true\nbeepan.com true\nbeercandle.com true\nbeeznest.com true\nbeframed.ch true\nbefundonline.de true\nbehere.be true\nbehoerden-online-dienste.de true\nbeikeil.de true\nbeinad.com true\nbeinad.ru true\nbelcompany.nl false\nbelievablebook.com false\nbelliash.eu.org true\nbelltower.io true\nbelly-button-piercings.com true\nbely-mishka.by true\nbemyvictim.com true\nben-energy.com false\nben.ninja true\nbenchling.com true\nbendechrai.com true\nbendemaree.com true\nbendingtheending.com true\nbendix.co true\nbeneathvt.com true\nbeneffy.com true\nbenhartmann.de true\nbenjamin.pe true\nbenjaminblack.net true\nbenjamins.com true\nbenmatthews.com.au true\nbenni1.eu true\nbenno.frl true\nbenny003.de true\nbenschnarr.com true\nbentertain.de true\nbentley.link true\nbentrask.com true\nbenzkosmetik.de true\nbeourvictim.com true\nberanovi.com true\nberger.work true\nbergstoneware.com false\nberlatih.com true\nberlin-kohlefrei.de true\nberlinleaks.com false\nbermeitinger.eu true\nbermytraq.bm true\nberr.yt true\nberra.se true\nberrymark.be true\nberst.cz true\nberthabailey.com true\nberyl.net true\nbespokestraps.com true\nbest-wedding-quotes.com true\nbestbeards.ca true\nbestbrakes.com true\nbestessayhelp.com true\nbesthost.cz true\nbestlashesandbrows.com true\nbestorangeseo.com true\nbetaclean.fr true\nbetafive.net true\nbetaworx.de true\nbetaworx.eu true\nbetcafearena.ro false\nbetlander.com true\nbetonmoney.com true\nbetpamm.com true\nbetplanning.it true\nbettercrypto.org true\nbetterhelp.com true\nbettingbusiness.ru true\nbettrlifeapp.com true\nbettween.com true\nbetulashop.ch true\nbevinco2020.com true\nbevinsco.org true\nbexit-hosting.nl true\nbexit-security.eu true\nbexit-security.nl true\nbexit.nl true\nbexithosting.nl true\nbeyuna.co.uk true\nbeyuna.eu true\nbeyuna.nl true\nbf.am false\nbfear.com true\nbfelob.gov true\nbfi.wien false\nbfw-online.de true\nbgcparkstad.nl true\nbgdaddy.com true\nbgneuesheim.de true\nbhatia.at true\nbhtelecom.ba true\nbhuntr.com true\nbiasmath.es true\nbiathloncup.ru true\nbible-maroc.com true\nbible.ru true\nbibleonline.ru true\nbiblerhymes.com true\nbiblionaut.net true\nbiddl.com true\nbielsa.me true\nbienenblog.cc true\nbienici.com true\nbierbaumer.net true\nbiergaizi.info true\nbig-andy.co.uk true\nbig-black.de true\nbigbluedoor.net true\nbigclassaction.com true\nbight.ca true\nbiguixhe.net true\nbike-shack.com true\nbikermusic.net true\nbikiniseli.com true\nbildermachr.de true\nbillaud.eu.org true\nbilliger-mietwagen.de true\nbilligssl.dk true\nbillin.net true\nbillogram.com true\nbillpro.com true\nbillpro.com.au true\nbilrom.com false\nbimbo.com true\nbimbobakeriesusa.com true\nbinaryevolved.com true\nbinarystud.io true\nbingofriends.com true\nbingostars.com true\nbiodieseldata.com true\nbioemsan.cz true\nbiofam.ru true\nbiolindo.com true\nbionicspirit.com true\nbiosbits.org true\nbioshome.de true\nbiosignalanalytics.com true\nbiosphere.cc true\nbiou.me true\nbip.gov.sa true\nbirdfeeder.online true\nbirminghamsunset.com true\nbit-sentinel.com true\nbit.voyage true\nbit8.com true\nbitbeans.de true\nbitbr.net true\nbitbucket.org false\nbitcoin-india.org true\nbitcoin.asia true\nbitcoin.ch true\nbitcoin.co.nz true\nbitcoin.de true\nbitcoin.im true\nbitcoin.info true\nbitcoin.us true\nbitcoinbitcoin.com true\nbitcoincore.org true\nbitcoinhk.org true\nbitcoinprivacy.net true\nbitcoinx.ro true\nbitex.la true\nbitf.ly true\nbitfehler.net true\nbitfinder.nl true\nbitfuse.net true\nbitgo.com true\nbitheus.com true\nbitlish.com true\nbitmex.com true\nbitminter.com true\nbitmon.net true\nbitnet.io true\nbitok.com true\nbitpod.de true\nbitref.com true\nbitshaker.net true\nbitskins.co true\nbittersweetcandybowl.com true\nbiurokarier.edu.pl true\nbivsi.com true\nbjornhelmersson.se true\nbjornjohansen.no true\nbl4ckb0x.com true\nbl4ckb0x.de true\nbl4ckb0x.eu true\nbl4ckb0x.info true\nbl4ckb0x.net true\nbl4ckb0x.org true\nblablacar.co.uk true\nblablacar.com true\nblablacar.com.tr true\nblablacar.com.ua true\nblablacar.de true\nblablacar.es true\nblablacar.fr true\nblablacar.hr true\nblablacar.hu true\nblablacar.in true\nblablacar.it true\nblablacar.mx true\nblablacar.nl true\nblablacar.pl true\nblablacar.pt true\nblablacar.ro true\nblablacar.rs true\nblablacar.ru true\nblack-armada.com true\nblack-armada.com.pl true\nblack-armada.pl true\nblackberrycentral.com true\nblackburn.link true\nblackdesertsp.com true\nblackhelicopters.net true\nblackly.uk true\nblackpayment.ru true\nblaise.io true\nblancodent.com true\nblastersklan.com true\nblaudev.es true\nblauerhunger.de true\nblauwwit.be true\nblazor.nl true\nblechschmidt.saarland true\nblendle.com true\nblendle.nl true\nblessnet.jp true\nblieque.co.uk true\nblingsparkleshine.com true\nblinkenlight.co.uk true\nblinkenlight.com.au true\nblmiller.com true\nblockchain.info true\nblocksatz-medien.de true\nbloemendal.me true\nblog.cyveillance.com true\nblog.gov.uk true\nblog.gparent.org true\nblog.linode.com false\nblog.torproject.org false\nblogarts.net true\nblognone.com true\nblogreen.org true\nblubberladen.de true\nblue-labs.org true\nblue-leaf81.net true\nblue42.net true\nblueflare.org true\nblueimp.net true\nbluemosh.com true\nblueperil.de true\nbluepoint.foundation true\nbluepoint.institute true\nbluescloud.xyz true\nblumenfeldart.com true\nblumiges-fischbachtal.de true\nblupig.net true\nblurringexistence.net true\nbluserv.net true\nblusmurf.net true\nbm-trading.nl true\nbmoattachments.org true\nbmone.net true\nbnhlibrary.com true\nbobancoamigo.com true\nbobcopeland.com true\nbockenauer.at true\nbodhi.fedoraproject.org true\nbodrumfarm.com true\nbodyblog.nl true\nboeddhashop.nl true\nboensou.com true\nboernecancerfonden.dk true\nbohramt.de true\nboilesen.com true\nboiseonlinemall.com true\nbokeyy.com true\nboltdata.io true\nbonapp.restaurant true\nbondskampeerder.nl true\nbonfi.net true\nbonifacius.be true\nbonobo.cz true\nboof.com true\nbooked.holiday true\nbookingapp.nl true\nbookmein.in true\nbookofraonlinecasinos.com true\nboomersurf.com true\nbootjp.me true\nborchers-media.de true\nboringsmith.com true\nboris64.net true\nborisbesemer.com true\nborrelioz.com true\nborysek.net true\nbotox.bz true\nbougeret.fr true\nbouncourseplanner.net true\nbouncyball.eu true\nbouncyballs.org true\nbourasse.fr true\nbourse-aux-jouets.org true\nbourse-aux-vetements.org true\nbourse-puericulture.org true\nbouwbedrijfpurmerend.nl true\nbowling.com true\nbownty.dk true\nboxcryptor.com true\nboxing-austria.eu true\nboxintense.com true\nboxpirates.to true\nboypoint.de true\nbpadvisors.eu true\nbpastudies.org true\nbqtoolbox.com true\nbr.search.yahoo.com false\nbradbrockmeyer.com true\nbradkovach.com true\nbrage.info true\nbrainster.co false\nbramvanaken.be true\nbran.land true\nbranchzero.com true\nbrandbuilderwebsites.com true\nbrandnewdays.nl false\nbrandonwalker.me true\nbrandred.net true\nbrasalcosmetics.com true\nbratteng.me true\nbratteng.xyz true\nbrd.ro true\nbreechdepot.com false\nbreeswish.org true\nbrefy.com true\nbregnedalsystems.dk true\nbreitbild-beamer.de true\nbrejoc.com true\nbrewtrackr.com true\nbrideandgroomdirect.ie true\nbridholm.se true\nbrightonbank.com true\nbrightstarkids.co.uk false\nbrightstarkids.com.au false\nbrightstarkids.net false\nbrightstarkids.sg false\nbrigidaarie.com true\nbrilliantbuilders.co.uk true\nbrilliantdecisionmaking.com true\nbritishscienceweek.org true\nbritzer-toner.de true\nbroadsheet.com.au true\nbroersma.com true\nbroeselei.at true\nbroken-oak.com true\nbronevichok.ru false\nbrookechase.com true\nbrossman.it true\nbrossmanit.com true\nbrownfieldstsc.org true\nbrrr.fr true\nbrunix.net true\nbrunn.email true\nbrunoramos.com true\nbrunoramos.org true\nbrunosouza.org true\nbryanquigley.com true\nbryn.xyz true\nbsidessf.com true\nbsklabels.com false\nbsquared.org true\nbta.lv false\nbtcdlc.com true\nbtcontract.com true\nbtio.pw true\nbtsoft.eu true\nbubblegumblog.com true\nbubulazi.com true\nbubulazy.com true\nbuch-cuber.de true\nbuchheld.at false\nbuck.com true\nbudaev-shop.ru true\nbuddhistische-weisheiten.org true\nbudgetalk.com true\nbudskap.eu true\nbuettgens.net true\nbuffalodrinkinggame.beer true\nbugcrowd.com true\nbugginslab.co.uk true\nbugs.chromium.org true\nbugtrack.io true\nbugzil.la true\nbugzilla.mozilla.org true\nbuhler.pro true\nbuiko.com true\nbuild.chromium.org false\nbuildbox.io true\nbuildkite.com true\nbuildsaver.co.za true\nbukkenfan.jp true\nbulktrade.de true\nbulldog-hosting.de true\nbulmafox.com true\nbunaken.asia true\nbunbun.be true\nbund-von-theramore.de true\nbundaberg.com true\nbunsenlabs.org true\nbureaubolster.nl true\nburi.be false\nburian-server.cz true\nburningcrash.de true\nburningflipside.com true\nburnworks.com true\nburtrum.me true\nburtrum.org true\nburzmali.com true\nbusindre.com true\nbusiness.facebook.com false\nbusiness.medbank.com.mt true\nbusinessesdirectory.eu true\nbusinessloanconnection.org true\nbuyinginvestmentproperty.com true\nbuzzconf.io true\nbvionline.eu true\nbwcscorecard.org true\nby4cqb.cn true\nbygningsregistrering.dk true\nbynet.cz true\nbyronwade.com true\nbyrtz.de true\nbyte.wtf true\nbytejail.com true\nbyteshark.org true\nbytesofcode.de true\nbytesund.biz true\nbytesystems.com true\nbyteturtle.eu true\nbziaks.xyz true\nbztech.com.br true\nbzv-fr.eu true\nc.cc true\nc16t.uk true\nc1yd3i.me true\nc3b.info false\nc3w.at true\nc4k3.net true\nca.gparent.org true\nca.search.yahoo.com false\ncaasd.org true\ncabarave.com true\ncablemod.com true\ncabsites.com true\ncabusar.fr true\ncacaolalina.com true\ncachethq.io true\ncackette.com true\ncaconnect.org true\ncadoth.net true\ncadusilva.com true\ncaesarkabalan.com true\ncaesreon.com true\ncaffeinatedcode.com true\ncais.de true\ncaizx.com true\ncaja-pdf.es true\ncajapopcorn.com true\ncajunuk.co.uk true\ncalaborlawnews.com true\ncalculator-imt.com true\ncalgoty.com true\ncalibreapp.com false\ncalibso.net true\ncalix.com true\ncall.me false\ncallear.org true\ncallhub.io true\ncallsigns.ca true\ncalltrackingreports.com true\ncalomel.org true\ncalories.org true\ncalvin.me true\ncalvinallen.net true\ncalyxengineers.com true\ncamaya.net true\ncambridgeanalytica.org true\ncamconn.cc true\ncamolist.com true\ncampaign-ad.com true\ncampaignelves.com true\ncampbellsoftware.co.uk true\ncampbrainybunch.com true\ncamperdays.de true\ncampermanaustralia.com true\ncamperverzekerd.nl true\ncampfiretails.org true\ncampfourpaws.com true\ncampus-finance.com true\ncanadalife.de true\ncanadasmotorcycle.ca true\ncanadianchristianity.com true\ncanarymod.net true\ncandicontrols.com true\ncando.eu true\ncandratech.com true\ncanhazip.com true\ncannyfoxx.me true\ncantrack.com true\ncanyonshoa.com true\ncanyoupwn.me true\ncao.gov true\ncao.la true\ncapecycles.co.za true\ncaphane.com true\ncapitalquadatv.org.nz true\ncapitaltg.com true\ncapogna.com true\ncapper.de true\ncapriccio.to true\ncaps.is true\ncaptivatedbytabrett.com true\ncapturapp.com true\ncapturethepen.co.uk true\ncaputo.com true\ncarano-service.de true\ncaraudio69.cz true\ncarboneselectricosnettosl.info false\ncarbonmade.com false\ncarck.co.uk true\ncardloan-manual.net true\ncardrecovery.fr true\ncardstream.com true\ncareerstuds.com true\ncaremad.io true\ncarey.li true\ncarezone.com false\ncarigami.fr true\ncarlandfaith.com true\ncarlgo11.com true\ncarnildo.com true\ncarsforbackpackers.com true\ncarsten.pw true\ncarstenfeuls.de true\ncartouche24.eu true\ncartucce24.it true\ncasa-su.casa true\ncasedi.org true\ncash-pos.com true\ncashew3d.com true\ncashlink.io true\ncasinolistings.com true\ncasinoreal.com true\ncasovi.cf true\ncastlejackpot.com true\ncat-box.de true\ncatalyst-ecommerce.com false\ncatarsisvr.com true\ncatgirl.pics true\ncativa.net true\ncatnet.dk false\ncatsmagic.pp.ua true\ncattivo.nl true\ncavac.at true\ncaveclan.org true\ncbhq.net true\nccayearbook.com true\nccblog.de true\nccja.ro false\ncdkeykopen.com true\ncdlcenter.com true\ncdnjs.com true\ncdt.org true\ncecipu.gob.cl true\nced-services.nl true\nceilingpac.org true\ncejhon.cz true\ncelti.ie.eu.org true\ncelti.name true\ncementscience.com true\nceml.ch true\ncentennialrewards.com true\ncenterpereezd.ru false\ncentillien.com true\ncentralpoint.be true\ncentralpoint.nl true\ncentralstatecu.org true\ncentralync.com true\ncentrepoint-community.com false\ncentricweb.com true\ncentrobill.com true\nceoimon.com true\nceopedia.org true\ncerastar.com true\ncerebelo.info true\nceritamalam.net true\ncertcenter.com true\ncertcenter.de true\ncertible.com true\ncertly.io true\ncertnazionale.it true\ncervejista.com true\ncesal.net true\ncesidianroot.eu true\ncesobaly.cz true\ncestlav.it true\nceu.edu false\ncevrimici.com true\nceyizlikelisleri.com true\ncfa.gov true\ncfcnexus.org true\ncfcproperties.com true\ncfh.com true\ncfo.gov true\ncfoitplaybook.com true\ncfxdesign.com true\ncg-systems.hu true\ncgan.pw true\ncgbilling.com true\ncgtx.us true\nch-sc.de true\nch.search.yahoo.com false\nchabaudparfum.com true\nchahub.com true\nchaletmanager.com true\nchamilo.org true\nchangetip.com true\nchaos-inc.de true\nchaos.fail true\nchaoschemnitz.de true\nchaosdorf.de true\nchaoswebs.net true\ncharge.co true\ncharityclear.com true\ncharitystreet.co.uk true\ncharl.eu true\ncharlierogers.com true\ncharmander.me true\nchartpen.com true\nchateau-belvoir.com true\nchatme.im false\nchatup.cf true\nchaulootz.com true\nchazay.net true\nchcemvediet.sk true\nchch.it true\ncheapestgamecards.de true\ncheapestgamecards.nl true\ncheapestgamecards.se true\ncheapgeekts.com false\ncheapgoa.com true\ncheck.torproject.org false\ncheckout.google.com true\nchecktype.com true\ncheckyourmath.com true\ncheerflow.com true\ncheetah85.de true\nchenapartment.com true\nchengl.com true\nchepaofen.com true\ncherrywoodtech.com true\nchfr.search.yahoo.com false\nchiaramail.com true\nchic-leather.com true\nchihiro.xyz true\nchikan-beacon.net true\nchildcaresolutionscny.org true\nchilihosting.eu true\nchimeratool.com true\nchina-line.org true\nchinternet.xyz true\nchiphell.com true\nchippy.ch false\nchiralsoftware.com true\nchireiden.net true\nchirgui.eu true\nchiropracticwpb.com true\nchiru.no true\nchit.search.yahoo.com false\nchloe.re true\nchloeallison.co.uk true\nchocolah.com.au false\nchocotough.nl false\nchoosemypc.net true\nchorpinkpoemps.de true\nchotu.net true\nchris-edwards.net true\nchris-web.info true\nchrisandsarahinasia.com true\nchrisbrown.id.au true\nchrisfaber.com true\nchrisfinazzo.com true\nchrishamper.com true\nchrisirwin.ca true\nchrisjean.com true\nchriskyrouac.com true\nchrismckee.co.uk true\nchristadelphiananswers.org true\nchristiaanconover.com true\nchristianbro.gq true\nchristianhoffmann.info true\nchristianliebel.com true\nchristiesantiques.com true\nchristina-quast.de true\nchristophheich.me true\nchriswarrick.com true\nchriswells.io true\nchrome-devtools-frontend.appspot.com true\nchrome.com false\nchrome.google.com true\nchromebooksforwork.com true\nchromiumcodereview.appspot.com false\nchronoshop.cz true\nchrst.ph true\nchsterz.de true\nchua.cf true\nchuckame.fr true\nchulado.com true\nchun.pro true\nchurchthemes.com true\nciat.no true\ncidbot.com true\ncienbeaute-lidl.fr true\ncigarblogs.net true\ncigarterminal.com false\ncimalando.eu false\ncimballa.com true\ncinefilzonen.se true\ncinema5.ru true\ncintdirect.com true\ncio.gov true\ncipherli.st true\ncirope.com true\ncirrus0.de true\nciscodude.net true\nciscohomeanalytics.com true\nciscommerce.net true\ncitizensbankal.com true\ncittadesign.com true\ncitya.com true\nciubotaru.tk true\ncjcaron.org true\nckleemann.de true\ncklie.de true\nckliemann.com true\nckliemann.net true\ncktennis.com true\ncl.search.yahoo.com false\nclaimconnect.us true\nclan-ww.com true\nclanthor.com true\nclapping-rhymes.com true\nclaralabs.com true\nclarkeaward.com true\nclassdojo.com true\nclassicday.nl true\nclassicsandexotics.com true\nclassicshop.ua true\nclawe.de true\ncldly.com true\nclearc.tk false\nclearkonjac.com true\nclearsettle-admin.com true\nclearviewwealthprojector.com.au true\nclevertarget.ru true\nclevisto.com true\nclickandshoot.nl true\nclickclickphish.com true\nclickenergy.com.au true\nclickforclever.com true\nclickphish.com true\nclicks.co.za true\nclientsecure.me true\nclifflu.net true\nclimaprecio.es true\nclimateinteractive.org true\nclimatestew.com true\ncliniquepariseau.com true\nclintwilson.technology true\nclip.ovh true\nclipped4u.com true\nclmde.de true\nclochix.net true\nclockcaster.com true\nclockworksms.com true\nclose.com false\ncloseli.cn true\ncloud-project.com true\ncloud.google.com true\ncloudbolin.es true\nclouddesktop.co.nz true\ncloudey.net true\ncloudflareonazure.com true\ncloudily.com true\ncloudmigrator365.com true\ncloudpagesforwork.com true\ncloudpebble.net true\nclouds.webcam true\ncloudsecurityalliance.org true\ncloudspace-analytics.com true\ncloudstorm.me true\ncloudstrike.co true\ncloudup.com true\ncloverleaf.net true\nclownish.co.il true\ncloxy.com true\nclsimplex.com true\nclu-in.org true\nclubmate.rocks true\nclubmini.jp true\nclubmix.co.kr true\nclvrwebdesign.com true\nclycat.ru true\ncmacacias.ch true\ncmahy.be true\ncmdline.org true\ncmlachapelle.ch true\ncmlancy.ch true\ncmlignon.ch true\ncmplainpalais.ch true\ncmsbattle.com true\ncnam.net true\ncni-certing.it true\ncnlic.com true\nco.search.yahoo.com false\nco50.com true\ncoachingconsultancy.com true\ncoam.co true\ncobalt.io false\ncocaine.ninja true\ncocker.cc false\ncocoaheads.at true\ncocolovesdaddy.com true\ncodabix.com true\ncodabix.de true\ncodabix.net true\ncode-poets.co.uk true\ncode-well.com true\ncode.facebook.com false\ncode.fm true\ncode.google.com true\ncode67.com true\ncodeco.pw true\ncodeferm.com true\ncodefordus.nrw true\ncodeforhakodate.org true\ncodefoundry.it true\ncodelayer.ca true\ncodeplay.org true\ncodepoints.net true\ncodepref.com true\ncodepult.com true\ncodereview.appspot.com false\ncodereview.chromium.org false\ncoderhangout.com true\ncodesport.io true\ncodeux.com true\ncodewild.de true\ncodewiththepros.org true\ncodeyellow.nl true\ncodingforspeed.com true\ncodyevanscomputer.com true\ncoffee-mamenoki.jp true\ncogent.cc true\ncogitoltd.com true\ncogumelosmagicos.org true\ncoi-verify.com true\ncoiffeurschnittstelle.ch true\ncoimmvest.com true\ncoinapult.com true\ncoinbase.com true\ncoinessa.com true\ncoinjar-sandbox.com true\ncojo.eu true\ncoldawn.com true\ncoldfff.com true\ncoldhak.ca true\ncolengo.com true\ncolisfrais.com true\ncollabornation.net true\ncollada.org true\ncollectiblebeans.com true\ncollegepulse.org true\ncollinmbarrett.com false\ncollins.kg true\ncollins.press true\ncollinsartworks.com true\ncoloradolottery.com true\ncolorbrush.ru true\ncoloringnotebook.com true\ncolorlib.com true\ncom.cc true\ncomarkinstruments.net true\ncombatshield.cz true\ncomdurav.com true\ncomercialtrading.eu true\ncomfortticket.de true\ncomhack.com true\ncomitesaustria.at true\ncomiteshopping.com true\ncommencepayments.com true\ncommerciallocker.com true\ncommoncore4kids.com true\ncommunityblog.fedoraproject.org true\ncomodo.nl true\ncomotalk.com true\ncompagnia-buffo.de true\ncomparamejor.com true\ncompareandrecycle.co.uk true\ncompareandrecycle.com true\ncompareinsurance.com.au true\ncomparejewelleryprices.co.uk true\ncomparetravelinsurance.com.au true\ncompeuphoria.com true\ncompiledworks.com true\ncompletionist.me true\ncompliance-systeme.de true\ncompliancedictionary.com true\ncomplymd.com true\ncompucorner.com.mx true\ncompucorner.mx true\ncomputersystems.guru true\ncomssa.org.au true\nconcentrade.de true\ncondepenalba.com true\nconfig.schokokeks.org false\nconnect.ua false\nconnected-verhuurservice.nl true\nconnectfss.com true\nconnectingconcepts.com true\nconnext.de true\nconnyduck.at true\nconrad-kostecki.de true\nconsciousandglamorous.com true\nconsciousbrand.co true\nconsciousbrand.org.au true\nconsciousbranding.org.au true\nconsciousbrands.net.au true\nconsole.support true\nconsonare.de true\nconstructionjobs.com true\nconsul.io true\nconsumer.gov true\nconsumersentinel.gov true\ncontactbig.com true\ncontainerstatistics.com true\ncontarkos.xyz true\ncontinuumgaming.com false\ncontributor.google.com true\ncontrolcenter.gigahost.dk true\nconve.eu true\nconvergemagazine.com true\nconversiones.com true\nconvert.zone true\nconverter.ml true\nconvocatoriafundacionpepsicomexico.org false\ncookiesoft.de true\ncookinglife.nl false\ncookmedical.com false\ncool110.tk true\ncoolaj86.com true\ncoopens.com true\ncoore.jp true\ncooxa.com true\ncopperhead.co true\ncor-ser.es true\ncoralproject.net true\ncordlessdog.com true\ncoreless-stretchfilm.com true\ncorgicloud.com true\ncornercircle.co.uk true\ncoronelpicanha.com.br true\ncortexitrecruitment.com true\ncoryadum.com true\ncostablancavoorjou.com true\ncostreportdata.com true\ncotonea.de true\ncoughlan.de true\ncountermail.com true\ncountybankdel.com true\ncoursdeprogrammation.com true\ncoursera.org true\ncourtlistener.com true\ncovenantoftheriver.org true\ncoverduck.ru true\ncovoiturage.fr false\ncovybrat.cz true\ncowboyim.com true\ncoweo.cz true\ncozmaadrian.ro true\ncpuvinf.eu.org true\ncpvmatch.eu true\ncqchome.com true\ncracker.in.th true\ncrackingking.com false\ncrackle.io true\ncrackpfer.de true\ncradlepointecm.com true\ncraftbeerbarn.co.uk true\ncraftedge.xyz true\ncraftinginredlipstick.com true\ncranesafe.com true\ncravelyrics.com true\ncrazycen.com true\ncrazydomains.ae true\ncrazydomains.co.nz true\ncrazydomains.co.uk true\ncrazydomains.com.au true\ncrazydomains.in true\ncrbug.com false\ncrea.me true\ncreativeartifice.com true\ncreativeplayuk.com true\ncreditkarma.com true\ncreditproautos.com true\ncreep.im true\ncrefelder.com true\ncrendontech.com true\ncrepererum.net true\ncrestasantos.com true\ncrestoncottage.com true\ncrimson.no true\ncristiandeluxe.com true\ncritical.today true\ncriticalaim.com true\ncrizk.com true\ncrl-autos.com true\ncrm.onlime.ch false\ncross-view.com true\ncrosscom.ch true\ncrossfitblackwater.com true\ncrosssec.com true\ncrosssellguide.com true\ncrow.tw true\ncrowd.supply true\ncrowdsupply.com true\ncrtvmgmt.com true\ncrufad.org true\ncrumbcontrol.com true\ncrushroom.com true\ncrute.me true\ncrypt.guru true\ncrypticshell.co.uk true\ncryptify.eu true\ncrypto.cat false\ncrypto.graphics true\ncryptobells.com false\ncryptobin.co true\ncryptocon.org true\ncryptography.io true\ncryptoisnotacrime.org true\ncryptojar.io true\ncryptolab.pro true\ncryptonit.net true\ncryptonym.com true\ncryptoparty.at true\ncryptoparty.dk true\ncryptopartyatx.org true\ncryptopartyutah.org true\ncryptopush.com true\ncryptoseb.pw true\ncrystalchandelierservices.com true\ncsacongress.org true\ncsapak.com true\ncsbs.fr true\ncselzer.com true\ncsfm.com true\ncsgodicegame.com true\ncsokolade.hu true\ncspvalidator.org true\ncssu.in true\ncstkit.com true\ncsuw.net true\ncsvape.com true\nct-status.org true\ncthomas.work true\nctns.de true\nctoforhire.com.au true\nctpe.net true\ncube.de true\ncubua.com true\ncuibonobo.com true\ncuisinezest.com true\ncujba.com true\nculinae.nl true\ncultiv.nl false\ncuonic.com true\ncup.al true\ncupcake.io true\ncupcake.is true\ncuracao-license.com true\ncuriosity-driven.org true\ncurlyroots.com true\ncurrency-strength.com true\ncurtacircuitos.com.br false\ncurveweb.co.uk true\ncuste.rs true\ncustodyxchange.com true\ncustomd.com true\ncuvva.co true\ncvjm-memmingen.de true\ncvmu.jp true\ncvr.dk true\ncvsoftub.com true\ncvursache.com true\ncwagner.me true\ncyberfrancais.ro true\ncyberguerrilla.info true\ncyberguerrilla.org true\ncyberhouse.at false\ncyberianhusky.com true\ncyberkov.com true\ncyberoptic.de true\ncyberpunk.ca true\ncybersecuritychallenge.be true\ncyberspect.com true\ncyberspect.io true\ncyberwire.nl true\ncybozu.cn true\ncybozu.com true\ncybozulive.com true\ncyclebeads.com true\ncyclehackluxembourgcity.lu true\ncyfly.org true\ncygnius.net true\ncymtech.net true\ncyon.ch true\ncyph.audio true\ncyph.com true\ncyph.im true\ncyph.video true\ncyprus-company-service.com true\ncysec.biz true\nczakey.net true\nczbix.com true\nczechamlp.com true\nczk.mk true\nczlx.co true\nd-20.fr true\nd-quantum.com true\nd-training.de true\nd3xt3r01.tk true\nd42.no true\nda-ist-kunst.de true\ndaallexx.eu true\ndachb0den.net true\ndad256.tk true\ndadons-laserdiscs.com true\ndadtheimpaler.com true\ndaduke.org true\ndaemon.xin true\ndag-konsult.com true\ndailyenglishchallenge.com true\ndailylifefinancial.com true\ndailystormerpodcasts.com true\ndaimadi.com true\ndairyshrine.org true\ndaiweihu.com true\ndale-electric.com true\ndalingk.co true\ndallmeier.net true\ndamianuv-blog.cz true\ndammekens.be true\ndamngoodpepper.com true\ndanaketh.com true\ndancerdates.net true\ndaniel-ruf.de true\ndanielalvarez.net true\ndanielcowie.me true\ndanielheal.net true\ndanielrozenberg.com true\ndanieltollot.de true\ndanielverlaan.nl true\ndanilapisarev.com true\ndanishenanigans.com true\ndanjesensky.com true\ndank.ninja true\ndankim.de true\ndannycrichton.com true\ndannyrohde.de true\ndanonsecurity.com true\ndanscomp.com true\ndanseressen.nl true\ndanskoferie.dk true\ndanw.io true\ndaphne.informatik.uni-freiburg.de true\ndaplie.com true\ndarchoods.net false\ndargasia.is true\ndario.im true\ndarioturchetti.me true\ndarkag.ovh true\ndarkdestiny.ch true\ndarkkeepers.dk true\ndarknebula.space false\ndarknode.in true\ndarkpony.ru true\ndarkserver.fedoraproject.org true\ndarkserver.stg.fedoraproject.org true\ndarkshop.nl true\ndarkside.re true\ndarksideof.it true\ndarkspacelab.com true\ndarkwater.info true\ndarlo.co.uk true\ndarom.jp true\ndarrenellis.xyz true\ndarrenm.net true\ndarrienworth.com true\ndarwinkel.net false\ndash-board.jp false\ndash.rocks true\ndashboard.yt true\ndata.haus true\ndata.qld.gov.au false\ndatabutlr.net true\ndatacalle.com true\ndatacandy.com true\ndatahove.no true\ndatajapan.co.jp true\ndatapun.ch true\ndatascience.cafe true\ndatasharesystem.com true\ndatasnitch.co.uk true\ndatatekniikka.com false\ndatatekniikka.fi false\ndateno1.com false\ndatorb.com false\ndatortipsen.se true\ndatsound.ru true\ndaveoc64.co.uk true\ndavevelopment.net true\ndavid-corry.com true\ndavid-schiffmann.de true\ndavid.kitchen true\ndavidandersson.se true\ndavidgouveia.net true\ndavidgow.net true\ndavidgrudl.com true\ndavidlyness.com true\ndavidmcevoy.org.uk true\ndavidmessenger.co.uk true\ndavidnoren.com true\ndavidscherzer.at true\ndavimun.org true\ndavisroi.com true\ndaylightpirates.org true\ndbgamestudio.com true\ndbldub.net true\ndbmteam.com true\ndc585.info true\ndccoffeeproducts.com true\ndckd.nl true\ndcmt.co true\ndcpower.eu true\ndd.art.pl true\ndden.ca false\ndden.xyz true\nddhosted.com true\nddmeportal.com true\nddos-mitigation.co.uk true\nddos-mitigation.info true\nde-medici.nl true\nde-spil.be true\nde.search.yahoo.com false\ndeadbeef.ninja true\ndealbanana.at true\ndealbanana.be true\ndealbanana.co.uk true\ndealbanana.com true\ndealbanana.de true\ndealbanana.fi true\ndealbanana.fr true\ndealbanana.it true\ndealbanana.se true\ndealcruiser.nl true\ndealpass.no true\ndeanmorgan.org true\ndeathy.ro true\ndecafu.co true\ndeco.me true\ndecoder.link true\ndecor-d.com true\ndecoratrix.com true\ndecoyrouting.com true\ndedelta.net true\ndedeo.tk true\ndedimax.de true\ndedmorozrzn.ru true\ndeduijventil.nl true\ndee.pe true\ndeepbluecrafting.co.uk true\ndeepcovelabs.net true\ndeeprecce.com true\ndeepserve.info true\ndeer.team true\ndeetzen.de true\ndef-pos.ru true\ndefcon.org true\ndegeberg.com true\ndegeberg.dk true\ndehopre.com true\ndeinballon.de true\ndelfic.org true\ndeliciisanatoase.ro true\ndelta-data.ch true\ndeltanet-production.de true\ndelvj.org true\ndementiapraecox.de true\ndemo.swedbank.se true\ndemocracy.io true\ndemocracychronicles.com true\ndemomanca.com false\ndemuzere.be true\ndenardbrewing.com true\ndenimio.com true\ndenisjean.fr true\ndennisdoes.net true\ndenniskoot.nl true\ndennogumi.org true\ndentallaborgeraeteservice.de true\ndentistglasgow.com true\ndentystabirmingham.co.uk true\ndenverprophit.us true\ndepechemode-live.com true\ndepicus.com true\ndepijl-mz.nl true\nderbyshire-language-scheme.co.uk true\nderchris.me true\ndereferenced.net true\nderegowski.net true\nderekkent.com true\ndergeilstestammderwelt.de true\nderhil.de false\nderp.army true\nderreichesack.com true\ndersoundhunter.de true\nderwolfe.net true\ndesiccantpackets.com true\ndesigngears.com true\ndesignthinking.or.jp false\ndesmaakvanplanten.be true\ndesserteagleselvenar.tk true\ndestinationbijoux.fr true\ndetectify.com false\ndethemium.com true\ndetoxsinutritie.ro true\ndetteflies.com true\ndetutorial.com true\ndeuxsolutions.com true\ndevb.nl true\ndevdoodle.net true\ndeveloper.mydigipass.com false\ndeveloperfair.com true\ndevelopers.facebook.com false\ndevelopfx.com true\ndevelopmentaid.org true\ndevh.net true\ndeviant.email true\ndevilshakerz.com true\ndeviltraxxx.de true\ndevklog.net true\ndevlux.ch true\ndevmsg.com true\ndevnsec.com true\ndevnull.team true\ndevolution.ws true\ndevopps.me true\ndevopsconnected.com true\ndevstaff.gr true\ndevuan.org true\ndewalch.net true\ndfekt.no true\ndfektlan.no true\ndfranke.com true\ndgeex.eu true\ndgt-portal.de true\ndhautefeuille.eu true\ndhauwer.nl true\ndhuy.net true\ndiamante.ro true\ndiarbag.us false\ndiasdasemana.com true\ndiasporadialogues.com true\ndibiphp.com true\ndicgaming.net true\ndicionariofinanceiro.com true\ndick.red true\ndidacte.com true\ndiddens.de true\ndie-besten-weisheiten.de true\ndie-blahuts.de true\ndie-partei-reutlingen.de true\ndiegelernten.de true\ndiegerbers.de true\ndienstplan.cc true\ndierenkruiden.nl false\ndieselgalleri.com true\ndieti.net true\ndietrich.cx true\ndighans.com true\ndigidroom.be true\ndigital-coach.it true\ndigital-eastside.de true\ndigital1st.co.uk true\ndigitaldeliarchive.com true\ndigitalehandtekeningen.nl true\ndigitallocker.com true\ndigitalnonplus.com true\ndigitalquery.com true\ndigitkon.com true\ndigminecraft.com true\ndijkmanmuziek.nl false\ndillonkorman.com true\ndime-staging.com true\ndime.io true\ndinepont.fr true\ndinge.xyz true\ndinkum.online true\ndinube.com true\ndiodeled.com true\ndipconsultants.com true\ndipl.io true\ndirectebanking.com true\ndirectme.ga true\ndirkwolf.de true\ndiscipul.nl true\ndiscofitta.com true\ndisconformity.net true\ndiscoveringdocker.com true\ndise-online.de true\ndisinclined.org true\ndisking.co.uk true\ndisorderboutique.com true\ndisposable.link true\ndisruptivelabs.net true\ndisruptivelabs.org true\ndissectcyber.com true\ndissimulo.me true\ndist.torproject.org false\ndistinctivephotography.com.au true\ndittvertshus.no true\ndivegearexpress.com true\ndiversityflags.com true\ndivingwithnic.com true\ndixiediner.com true\ndixmag.com true\ndiybook.at true\ndiycc.org true\ndizorg.net true\ndjlive.pl true\ndjxmmx.net false\ndjz4music.com true\ndk.search.yahoo.com false\ndkds.us true\ndl.google.com true\ndlc.viasinc.com true\ndlemper.de true\ndlscomputers.com.au true\ndm.lookout.com false\ndm.mylookout.com false\ndmcibulldog.com true\ndmeevalumate.com true\ndmfd.net true\ndmi.es true\ndmlogic.com true\ndmwall.cn true\ndmxledlights.com true\ndmz.ninja true\ndn42.eu true\ndn42.us true\ndna.li true\ndnc.org.nz true\ndne.lu true\ndnmlab.it true\ndns.google.com true\ndnscrypt.org true\ndnscurve.io true\ndnshallinta.fi true\ndnslog.com true\ndnsman.se true\ndoc.python.org true\ndocbox.ch true\ndocemeldoces.com true\ndocid.io true\ndocket.news true\ndocloh.de true\ndocs.google.com true\ndocs.python.org true\ndoctorfox.co.uk true\ndoctorwho.cz true\ndocucopies.com true\ndocufiel.com true\ndoesmycodehavebugs.today true\ndogan.ch false\ndogbox.se true\ndogespeed.ga true\ndogfi.sh true\ndoggieholic.net true\ndogoodbehappyllc.com true\ndoku-gilde.de true\ndokuboard.com true\ndolarcanadense.com.br true\ndollarstore24.com true\ndollemore.com true\ndolphin-cloud.com true\ndolphin-hosting.com true\ndolphin-it.de true\ndolphincorp.co.uk true\ndolphinswithlasers.com true\ndomadillo.com true\ndomainexpress.de true\ndomainkauf.de true\ndomains.google.com true\ndomainstaff.com true\ndomfee.com true\ndomhaase.me true\ndominikkulaga.pl true\ndominique-mueller.de true\ndomodedovo.travel true\ndonateaday.net true\ndonmez.uk false\ndonmez.ws false\ndonner-reuschel.de true\ndonnons.org true\ndonotcall.gov true\ndonotspellitgav.in true\ndontcageus.org true\ndonthedragonwilson.com true\ndoodledraw.ninja true\ndoordecor.bg true\ndopost.it true\ndoppenpost.nl true\ndorianharmans.nl true\ndorianmuthig.com true\ndossplumbing.co.za true\ndot.ro true\ndotbigbang.com true\ndotbox.org true\ndotsiam.com true\ndoubleavineyards.com true\ndoublefun.net true\ndougferris.id.au true\ndovecotadmin.org true\ndovetailnow.com true\ndownloadsoftwaregratisan.com true\ndpd.com.pl true\ndprd-wonogirikab.go.id true\ndpsg-roden.de true\ndr2dr.ca true\ndragon-chem.eu true\ndragonisles.net true\ndragonschool.org true\ndrahcro.uk true\ndrainagebuizen.nl true\ndramaticpeople.com true\ndranderle.com true\ndranek.com true\ndraw.uy true\ndrawesome.uy true\ndrawingcode.net true\ndrdevil.ru true\ndreamcreator108.com true\ndreamlinehost.com true\ndreamsforabetterworld.com.au true\ndreamtechie.com true\ndredgepress.com true\ndreid.org true\ndreizwosechs.de true\ndrew.red true\ndreweryinc.com true\ndrhopeson.com true\ndriesjtuver.nl true\ndriftdude.nl true\ndrino.org false\ndrishti.guru true\ndrive.google.com true\ndrivenes.net true\ndriving-lessons.co.uk true\ndrkmtrx.xyz true\ndrobniuch.pl false\ndroidwiki.de false\ndronepit.dk true\ndroomhuis-in-friesland-kopen.nl true\ndropbox.com true\ndropboxer.net true\ndrpetervoigt.de true\ndrrr.chat true\ndrugagodba.si true\ndrumbe.at true\ndrupal.org true\ndrycreekphoto.com true\ndsbrowser.com true\ndsebastien.net true\ndsol.hu true\nduckduckstart.com true\nducohosting.com true\nduelysthub.com true\nduernberg.at true\nduijf.info true\nduijfathome.nl true\ndukun.de true\ndumino.bg true\nduncancmt.com true\ndunea.nl true\nduo.money true\ndurangoenergyllc.com true\nduria.de true\ndurys.be true\ndustri.org true\ndutch1.nl true\ndutchrank.com true\ndutchrank.nl true\ndutchwanderers.nl true\ndutchweballiance.nl true\ndutyfreeonboard.com true\nduuu.ch true\ndvbris.co.uk true\ndvbris.com true\ndvorupotocnych.sk true\ndvotx.org true\ndvwc.org true\ndwhd.org true\ndworzak.ch true\ndxa.io true\ndyeager.org true\ndynaloop.net true\ndynamicnet.net true\ndynamicsnetwork.net true\ndyrenesverden.no true\ndziekonski.com true\ndzyabchenko.com true\ne-kontakti.fi true\ne-lifetechnology.com true\ne-mak.eu true\ne-teacher.pl true\ne-tmf.org true\ne-typ.eu true\ne.mail.ru true\ne3amn2l.com true\ne3kids.com true\neagleridgecampground.com true\neagletechz.com.br true\neagleyecs.com true\neames-clayton.us true\nearga.sm true\nearmarks.gov true\neasez.net true\neastmontgroup.com true\neasy-rpg.org true\neasyconstat.com true\neasycosmetic.ch true\neasykonto.de true\neasyplane.it true\neasypv.ch true\neasysimplecrm.com false\neatery.co.il true\neatlowcarb.de true\neatsleeprepeat.net true\neatvisor.co.uk true\neauclairecommerce.com true\neb7.jp true\nebankcbt.com true\nebanking.indovinabank.com.vn true\nebcs-solutions.com true\nebermannstadt.de false\nebfe.pw false\nebp2p.com true\nebpglobal.com true\necchidreams.com true\necco-verde.com true\nechipstore.com true\nechomanchester.net true\nechosixmonkey.com true\neckel.co true\neckro.com true\necnetworker.com true\necogen.com.au true\necogen.net.au true\neconsumer.gov true\necorus.eu true\necosystem.atlassian.net true\necrimex.net true\nectora.com true\necupcafe.com false\ned.gs true\nedakoe.ru true\nedati.lv true\neddmixpanel.com true\nedelblack.ch true\nedesseglabor.hu true\nedgereinvent.com true\nedicct.com true\nediscomp.sk true\nedissecurity.sk true\nedit.yahoo.com false\nedk.com.tr true\nedmundcelis.com true\nedoss.co.za true\nedp-collaborative.com true\nedpubs.gov true\neducationunlimited.com true\neducator-one.com true\neduid.se true\neduif.nl true\neduroam.no true\nedvmesstec.de true\nedxg.de true\nedyou.eu true\neelsden.net true\neelzak.nl true\neeqj.com true\neery.de true\nef.gy true\neff.org true\nefficienthealth.com true\neffortlesshr.com true\negfl.org.uk true\negg-ortho.ch true\nego4u.com true\nego4u.de true\negretail.no true\nehipaa.com true\nehipaadev.com true\nehrenamt-skpfcw.de true\nehrlichesbier.de true\nehuber.info true\neichornenterprises.com true\neickemeyer.nl true\neimacs.com true\neinaros.is true\neinheizpreis.de true\neinsatzstellenverwaltung.de true\neintageinzug.de true\neirastudios.co.uk false\neit-web.de true\nejeff.org true\nekbanden.nl false\nekokontakt.cz true\nekostecki.de true\neksisozluk.com true\nekzarta.ru true\nel-soul.com true\nelanguest.pl true\nelanguest.ro true\nelanguest.ru true\nelars.de true\nelbetech.net true\nelearningpilot.com true\nelectricant.com true\nelectricant.nl true\nelectronic-ignition-system.com true\nelectronicfasteners.com true\neleicoes2016.com.br true\nelektropost.org true\nelement-43.com true\nelemental.software true\nelementalrobotics.com true\nelemenx.com true\nelemprendedor.com.ve true\nelena-baykova.ru true\nelenag.ga true\nelephpant.cz true\nelglobo.com.mx true\nelgosblanc.com true\neligible.com true\neligibleapi.com true\neligrey.com true\nelisa.ee false\nelisabeth-kostecki.de true\nelisabethkostecki.de true\nelite12.de true\nelitefishtank.com false\nelitegameservers.net true\nelitehosting.de true\nella-kwikmed.com true\nellegaard.dk true\nelliquiy.com true\nelliriehl.at true\nellsinger.me true\nelmermx.ch true\nelnan.do true\neloge.se true\nelonbase.com true\nelpo.net true\nelsamakhin.com true\neltagroup.co.uk true\nelternforum-birmensdorf.ch true\neluhome.de true\nelvidence.com.au true\nem-biotek.cz true\nemailfuermich.de true\nemailhunter.co true\nemailprivacytester.com true\nemaily.eu true\nemanuelduss.ch true\nembracethedarkness.co.uk true\nembroideryexpress.co.uk true\nemi-air-comprime.com true\nemielraaijmakers.nl true\nemilong.com true\nemilyhorsman.com true\nemilyshepherd.me true\nemirabiz.com false\nemjainteractive.com true\nempleosentorreon.mx true\nempowerdb.com true\nemprego.pt true\nemptypath.com true\nemrenovation.com true\nen-maktoob.search.yahoo.com false\nenaim.de true\nencfs.win true\nencircleapp.com true\nencnet.de true\nencoder.pw true\nencredible.de true\nencredible.org true\nencryptallthethings.net true\nencrypted.google.com true\nencryptedaudience.com true\nencryptio.com true\nend.pp.ua true\nender.co.at true\nendlessdark.net true\nendlessdiy.ca true\nendlesshorizon.net true\nendlesstone.com true\nendofnet.org true\nendohaus.com true\nenecoshop.nl true\nenefan.jp true\nenergiekeurplus.nl true\nenergy-drink-magazin.de true\nenergy-infra.nl true\nenfoqueseguro.com true\nengelundlicht.ch true\nengineeryourmarketing.com true\nenglerts.de true\nenglishbulgaria.net true\nenglishclub.com true\nenglishforums.com true\nenjen.net true\nenjoy-nepal.de true\nenlightenedhr.com true\nenloestatebank.com true\nenorekcah.com true\nenquos.com true\nenscosupply.com true\nensemble-rubato.de true\nenskat.de true\nenskatson-sippe.de true\nensured.com true\nensured.nl true\nenteente.club true\nenteente.com true\nenteente.space true\nenteente.xyz true\nenterprisecarclub.co.uk true\nenterprisey.enterprises true\nentersynapse.com false\nenumify.com true\nenvelope.co.nz true\nenveloppenopmaat.nl true\nenviam.de true\nenvygeeks.com true\nenvygeeks.io true\neoldb.org true\neopugetsound.org true\nepanurse.com true\nepay.bg true\nepaygateway.net true\nephry.com true\nepicsecure.de true\nepicsoft.de true\nepostplus.li true\nepublibre.org true\neq-serve.com true\neqorg.com true\nequilibre-yoga-jennifer-will.com true\nequipsupply.com true\nerawanarifnugroho.com true\nerepublik-deutschland.de true\nericdiao.com true\nerichorstmanshof.nl true\nericwie.se true\nericyl.com true\nerigrid.eu true\neriix.org true\nerisrenee.com true\nernesto.at true\nerp-band.ru true\nerp.band true\nerpband.ru true\nerpcargo.com true\nerrlytics.com true\nersindemirtas.com true\neru.me true\nerudikum.cz true\nerwinwensveen.nl true\nes.search.yahoo.com false\nescalate.eu true\nescapees.com true\nesclear.de true\nescritoriodearte.com true\nesg-abi2001.de true\nesigmbh.de true\nesko.bar true\neskriett.com true\nesn-ypci.com true\nesoa.net true\nesocweb.com true\nespacemontmorency.com true\nespanol.search.yahoo.com false\nespci.fr true\nespgg.org true\nesquonic.com true\ness-cert.ru true\nessentialoilsimports.com true\nesseriumani.com true\nessexghosthunters.co.uk true\nessoduke.org true\nestaleiro.org true\nesteam.se true\nestebanborges.com true\nestilosapeca.com true\nestrietoit.com true\netaes.eu true\netdonline.co.uk true\neteesheet.com true\netelej.com true\neth0.nl true\netha.nz true\nethack.org true\nethanfaust.com true\nethercalc.com true\nethercalc.org true\nethicaldata.co.uk true\nethicall.org.uk true\nethil-faer.fr true\nethitter.com true\nethlan.fr true\nethosinfo.com true\netkaddict.com true\netrker.com true\nets2mp.de true\netula.me true\netyd.org true\neugenekay.com true\neulenleben.de true\neulerpi.io true\neuro.ro false\neuropop.com true\neuroshop.or.at true\neuroshop24.net true\neurostrategy.vn.ua true\neurotramp.com true\nev-zertifikate.de true\neva.cz true\nevalesc.com true\nevantage.org true\nevantageglobal.com true\nevasovova.cz true\nevegalaxy.net true\neventaro.com true\neverhome.de true\neverling.lu true\neverylab.org true\neverymove.org true\neverythingkitchens.com false\neveseat.net true\neveshamglass.co.uk true\nevi.be true\nevilized.de true\nevites.me true\nevolutionlending.co.uk true\nevomon.com true\nevowl.com false\nevrial.com true\nevstatus.com true\newe2.ninja true\newie.name true\nexceltobarcode.com true\nexcessamerica.com true\nexchangeworks.co true\nexekutori.com true\nexemples-de-stands.com true\nexeria.de true\nexfiles.cz true\nexgaywatch.com true\nexgravitus.com true\nexiahost.com true\nexon.io true\nexoscale.ch true\nexoticads.com true\nexpatads.com true\nexperienceoz.com.au true\nexpertmile.com true\nexperts-en-gestion.fr true\nexplodie.org true\nexploit.cz true\nexpo-asia.ru true\nexpo-designers.com true\nexpress-vpn.com true\nexpressemotion.net true\nexpresshosting.org true\nexpressvpn.com true\nexpxkcd.com true\nextendwings.com true\nextracobanks.com true\nextranetpuc.com.br true\nextremenetworking.net true\nexy.pw true\nexyplis.com true\neyasc.nl true\neydesignguidelines.com true\neyeonid.com true\neytosh.net true\neyyit.com true\nez.fi true\nezrefurb.co.uk true\nf-be.com true\nf-droid.org true\nf-s-u.co.uk true\nfa-works.com true\nfaber.org.ru true\nfabienbaker.com true\nfableforge.nl true\nfabse.net true\nfacebook.com false\nfachschaft-informatik.de true\nfactor.cc false\nfactorable.net true\nfactuursturen.be true\nfactuursturen.nl true\nfactys.do true\nfactys.es true\nfadednet.com true\nfadilus.com true\nfaeriecakes.be true\nfahrenwal.de true\nfailproof.be true\nfairbill.com true\nfaircom.co.za true\nfairlyoddtreasures.com true\nfaizan.net true\nfaktura.pl true\nfakturi.com true\nfakturoid.cz true\nfalconvintners.com true\nfalkena.net true\nfallenangeldrinks.co.uk true\nfallenangeldrinks.com true\nfallenangeldrinks.eu true\nfallenangelspirits.co.uk true\nfallenangelspirits.com true\nfallenangelspirits.uk true\nfallenspirits.co.uk true\nfalsum.net true\nfamilie-kupschke.de true\nfamilie-monka.de true\nfamilie-sander.rocks true\nfamiliegrottendieck.de true\nfamilieholme.de true\nfamiljenfrodlund.se true\nfamiljenm.se true\nfandomservices.com true\nfangs.ink true\nfanjoe.be true\nfant.dk true\nfanvoice.com true\nfanyue123.tk true\nfap.no true\nfaq.lookout.com false\nfaretravel.co.uk true\nfargtorget.se true\nfarhood.org true\nfarmacialaboratorio.it true\nfasdoutreach.ca true\nfashion.net true\nfaspirits.co.uk true\nfaspirits.com true\nfassadenverkleidung24.de true\nfasset.jp true\nfastaim.de true\nfastcomcorp.com true\nfastcomcorp.net true\nfastmail.com false\nfastopen.ml true\nfatlossguide.xyz true\nfatwin.pw true\nfatzebra.com.au false\nfaucetbox.com true\nfawkex.me true\nfawong.com true\nfca-tools.com true\nfcburk.de true\nfdsys.gov false\nfdt.name true\nfearsomegaming.com true\nfecik.sk true\nfederalregister.gov true\nfedericomigliavacca.it true\nfedorahosted.org true\nfedorapeople.org true\nfedrtc.org true\nfedux.com.ar true\nfeedbin.com false\nfeedhq.org true\nfeedthebot.com true\nfeel.aero true\nfeen.us true\nfeezmodo.com true\nfelisslovakia.sk true\nfeliwyn.fr false\nfelixrr.pro true\nfenno.net true\nferagon.net true\nferienhaus-polchow-ruegen.de false\nfernandes.org true\nfernandob.com true\nfernandobarillas.com true\nferrugem.org true\nfestrip.com true\nfetch.co.uk true\nfeuerwehr-oberkotzau.de true\nfewo-thueringer-wald.de true\nfexco.com true\nff-bad-hoehenstadt.de true\nffbans.org true\nffmradio.de true\nfhcdn.xyz true\nfi.google.com true\nfi.search.yahoo.com false\nficus.io true\nfid.to true\nfidanza.eu true\nfidel.uk true\nfidelapp.com true\nfiendishmasterplan.com true\nfierlafijn.net true\nfierman.eu true\nfierman.net true\nfierman.us true\nfiguurzagers.nl true\nfiilr.com true\nfiken.no true\nfiksel.info true\nfikt.space true\nfile-pdf.it true\nfiledir.com false\nfilemeal.com true\nfilestar.io true\nfilhodohomem.com true\nfilip-prochazka.com true\nfilippo.io true\nfillmysuitca.se true\nfiloo.de true\nfinancieringsportaal.nl true\nfinditez.com true\nfindyour.diet true\nfinenet.com.tw true\nfinfev.de true\nfingent.com true\nfinisron.in true\nfinkelstein.fr true\nfinn.io true\nfinneas.net true\nfinpt.com true\nfionamcbride.com true\nfirebird.io true\nfirebirdrangecookers.com true\nfirecore.com false\nfirefall.rocks true\nfirefart.at false\nfirehost.com true\nfireorbit.de true\nfirevap.org true\nfirexarxa.de true\nfirma-offshore.com true\nfirmapi.com true\nfirst-time-offender.com true\nfirstchoicecandy.com true\nfirstderm.com true\nfirstforex.co.uk true\nfirstlook.org true\nfirstmall.de true\nfischers.cc true\nfish-hook.ru true\nfit4medien.de true\nfitbylo.com true\nfitiapp.com true\nfitkram.cz true\nfitnesswerk.de true\nfittelo.cz true\nfitzsim.org true\nfivestarsitters.com true\nfiws.net true\nfixatom.com true\nfixhotsauce.com true\nfixmycomputerdude.com true\nfizz.buzz true\nfj.simple.com false\nflaemig42.de true\nflagspot.net true\nflajshans.cz true\nflamer-scene.com false\nflamingcow.tv true\nflamingkeys.com true\nflamme-von-anor.de true\nflareon.net true\nflat.io true\nflawcheck.com true\nflawlesscowboy.xyz true\nflazznetworks.com false\nfletchto99.com true\nflexapplications.se true\nfleximus.org false\nflexinvesting.fi true\nflexport.com true\nflightschoolbooking.com true\nflipagram.com false\nflipkey.com true\nflipneus.net true\nflirchi.com false\nfloaternet.com true\nflocktofedora.org true\nfloobits.com true\nfloorball-haunwoehr.de true\nfloort.net true\nflorence.uk.net true\nflorent-tatard.fr true\nflorian-schlachter.de true\nflorianmitrea.uk true\nfloridaescapes.co.uk true\nfloridafieros.org true\nflorismouwen.com true\nfloskelwolke.de true\nflouartistique.ch true\nflow.pe true\nflowersandclouds.com true\nfloweslawncare.com true\nflowlo.me true\nflra.gov true\nfluidojobs.com true\nflukethoughts.com true\nfluxent.de false\nfluxfingers.net true\nfly.moe true\nflynn.io true\nflyserver.co.il true\nfmarchal.fr true\nfnb-griffinonline.com true\nfndout.com true\nfniephaus.com true\nfnordserver.eu true\nfocusmark.jp false\nfokkusu.fi true\nfolv.es true\nfondanastasia.ru true\nfont-converter.net true\nfoodacademy.capetown true\nfoodiebox.no true\nfoodievenues.com true\nfoodwise.marketing true\nforay-jero.me true\nforbook.net true\nfordbydesign.com true\nforeveralone.io true\nforewordreviews.com true\nforextimes.ru true\nforgix.com true\nformationseeker.com true\nformationsfactory.co.uk false\nformazioneopen.it true\nformbetter.com true\nforo.io false\nforodeespanol.com true\nforschbach-janssen.de true\nfortesanshop.it true\nfortnine.ca true\nfortress.sk true\nfortworth.ch true\nforum.linode.com false\nfossewaygardencentre.co.uk true\nfotocerita.net true\nfotofaerie.net true\nfotopasja.info true\nfotostudio-schweiz.ch true\nfotowettbewerb.co true\nfotowolfy.com true\nfourchin.net true\nfoxbnc.co.uk true\nfoxdev.co true\nfoxley-farm.co.uk true\nfoxley-seeds.co.uk true\nfoxleyseeds.co.uk true\nfoxtrot.pw true\nfpy.cz true\nfr.search.yahoo.com false\nfr33d0m.link true\nfrack.nl true\nfragnic.com true\nfraho.eu true\nfralef.me false\nfrancescopalazzo.com true\nfrancevpn.xyz true\nfranckgirard.net true\nfrangor.info true\nfrank.fyi true\nfrankhaala.com true\nfrankierprofi.de true\nfranta.biz true\nfranta.email true\nfranzt.ovh true\nfrasys.cloud true\nfrasys.io true\nfrasys.net true\nfraye.net true\nfrederik-braun.com true\nfredvoyage.fr true\nfree.com.tw true\nfreeboson.org true\nfreebus.org true\nfreedom.press false\nfreekdevries.nl true\nfreelance.boutique true\nfreelance.nl true\nfreemanning.de true\nfreemyipod.org true\nfreenetproject.org true\nfreeshell.de true\nfreesitemapgenerator.com true\nfreesoftwaredriver.com true\nfreesounding.com true\nfreesounding.ru true\nfreethought.org.au true\nfreetsa.org true\nfreeweibo.com true\nfreifunk-essen.de true\nfreifunk-luenen.de true\nfreiwurst.net true\nfresh-hotel.org true\nfreshdns.nl true\nfrezbo.com true\nfrforms.com true\nfrickelboxx.de true\nfrickenate.com true\nfridolinka.cz true\nfrillip.com true\nfrippz.se true\nfritzrepair.com true\nfrizo.com true\nfrly.de true\nfrob.nl true\nfrogatto.com true\nfrogeye.fr true\nfromlemaytoz.com true\nfromscratch.rocks true\nfronteers.nl true\nfrontisme.nl true\nfrontmin.com true\nfroot.se true\nfroxlor.support true\nfrtn.com true\nfrtr.gov true\nfruchthof24.de true\nfrugro.be true\nfruitusers.com true\nfrusky.de true\nfsapubs.gov true\nfsbpaintrock.com true\nfsbturton.com true\nfsfi.is true\nfsm2016.org true\nftc.gov false\nftccomplaintassistant.gov true\nfteproxy.org true\nfugle.de true\nfuglede.dk true\nfukushima-web.com true\nfumiware.com true\nfumo.se true\nfunctions-online.com true\nfundacionhijosdelsol.org true\nfundays.nl true\nfunderburg.me true\nfundingempire.com true\nfuni4u.com true\nfunnyang.com true\nfunrun.com true\nfurgo.love true\nfurkancaliskan.com true\nfurry.dk true\nfusedrops.com true\nfusionmate.com true\nfutbol11.com true\nfutos.de true\nfuturesonline.com true\nfuwafuwa.moe true\nfuzzing-project.org true\nfx5.de true\nfxp.co.il true\nfyn.nl true\nfysiohaenraets.nl true\ng-m-w.eu true\ng-marketing.ro true\ng-o.pl true\ng2a.co true\ng4w.co true\ng5led.nl true\ngabber.scot true\ngaelleetarnaud.com true\ngafachi.com true\ngagniard.org true\ngagor.pl true\ngaichon.com true\ngakkainavi-epsilon.jp true\ngakkainavi-epsilon.net true\ngakkainavi.jp true\ngakkainavi.net true\ngakkainavi4.com true\ngakkainavi4.jp true\ngakkainavi4.net true\ngalactic-crew.org true\ngalenskap.eu true\ngam3rs.de true\ngamajo.com true\ngambit.pro true\ngambitnash.co.uk true\ngambitnash.com true\ngambitprint.com true\ngame-files.net true\ngame7.de true\ngamecard-shop.nl true\ngamecave.de true\ngamecollector.be true\ngamedevelopers.pl true\ngamegix.com true\ngameisbest.jp true\ngamenected.com true\ngamepad.vg true\ngamepader.com true\ngameparade.de true\ngamercredo.com true\ngamercredo.net true\ngamers-life.fr true\ngamerslair.org true\ngamesurferapp.com true\ngamingmedia.eu true\ngamingreinvented.com true\ngamingzoneservers.com true\ngamishou.fr true\ngamoloco.com true\ngampenhof.de true\nganhonet.com.br true\ngar-nich.net true\ngarantieabschluss.de false\ngarbage-juice.com true\ngaredtech.com true\ngarron.net true\ngaryjones.co.uk true\ngasbarkenora.com true\ngasnews.net true\ngateworld.fr true\ngatilagata.com.br true\ngaussorgues.me false\ngautvedt.no true\ngavick.com true\ngaytorrent.ru true\ngc-mc.de true\ngc.net true\ngcs-ventures.com true\ngcsepod.com true\ngdpventure.com true\nge1.me false\nge3k.net true\ngeblitzt.de true\ngechr.io true\ngee.is true\ngeek-hub.de true\ngeek.com.tw true\ngeekandi.com true\ngeeklair.net true\ngeekwu.org true\ngeeky.software true\ngeeq.ch true\ngehaowu.com true\ngeli-graphics.com true\ngelog-software.de true\ngemeentemolenwaard.nl true\ngemeinfreie-lieder.de true\ngemini.com true\ngendrin.com true\ngeneau.net true\ngenehome.com.au true\ngenerali-worldwide.com true\ngenerationnext.pl true\ngeneric.cx true\ngenossen.ru true\ngenshiken-itb.org true\ngenshiken.org true\ngenslerwisp.com true\ngenuxation.com true\ngenuxtsg.com true\ngenxbeats.com true\ngenxnotes.com true\ngenyhitch.com true\ngeoip.fedoraproject.org true\ngeoip.stg.fedoraproject.org true\ngeolad.com true\ngeorgemaschke.com true\ngeorgemaschke.net true\ngeorgesonarthurs.com.au true\ngeorgmayer.eu true\ngeoscan.aero true\ngeraintwhite.co.uk true\ngerardozamudio.mx true\ngerencianet.com.br false\ngereon.ch true\ngermandarknes.net true\ngernert-server.de true\ngersting.net true\ngeschwinder.net true\ngesiwista.net true\nget-asterisk.ru true\nget-erp.ru true\nget.zenpayroll.com false\ngetbox.me true\ngetbutterfly.com true\ngetcarina.com true\ngetcloak.com false\ngetcolor.com true\ngetdash.io true\ngetdigitized.net true\ngeterp.ru true\ngetfedora.org true\ngetfittedstore.com true\ngetflorence.co.uk true\ngethttpsforfree.com true\ngetinternet.de true\ngetkai.co.nz true\ngetmango.com true\ngetnikola.com true\ngetremembrall.com true\ngetsello.com false\ngetsensibill.com true\ngetsetupfile.com true\ngetspire.com true\ngetsport.mobi true\ngetts.ro true\ngetvdownloader.com true\ngflclan.ru true\nggp2.com true\nggservers.com true\nggx.us true\ngha.st true\nghcif.de true\ngheorghesarcov.ga true\nghostblog.info true\ngiacomopelagatti.it true\ngiant-powerfit.co.uk true\ngianttree.de true\ngiftgofers.com true\ngiftsn.com.sg false\ngifzilla.net false\ngig.ru false\ngigacog.com true\ngigawa.lt true\ngijsbertus.com true\ngilcloud.com true\ngilgaz.com true\ngingali.de true\nginja.co.th true\ngintenreiter-photography.com true\nginzadelunch.jp true\ngipsic.com true\ngirlsnet.work true\ngistfy.com true\ngit.co true\ngithub.com true\ngiveattheoffice.org false\ngivemyanswer.com true\ngivingnexus.org false\ngix.net.pl true\ngixtools.co.uk true\ngixtools.com true\ngixtools.net true\ngixtools.uk true\ngjcampbell.co.uk true\ngjspunk.de true\ngjung.com false\nglasgestaltung.biz true\nglass.google.com true\nglasschmuck-millefiori.de true\nglidingshop.cz true\nglidingshop.de true\nglidingshop.eu true\nglitchsys.com true\nglittersjabloon.nl true\nglobalcomix.com true\nglobalexpert.co.nz true\nglobalgivingtime.com true\nglobalinstitutefortraining.org.au true\nglobalmusic.ga true\nglobalperspectivescanada.com true\nglobalsites.nl true\nglobuli-info.de true\nglossopnorthendafc.co.uk true\nglubbforum.de true\ngm-assicurazioni.it true\ngmail.com false\ngmcd.co true\ngmdu.net true\ngmta.nl true\ngmw-hannover.de true\ngmw-ingenieurbuero.de true\ngnetwork.eu true\ngo-zh.org true\ngo.xero.com false\ngo2sh.de true\ngo4it.solutions true\ngoalsetup.com true\ngoaltree.ch true\ngoat.xyz true\ngocardless.com true\ngodesigner.ru true\ngoerner.me true\ngoerres2014.de true\ngofigure.fr false\ngogenenglish.com true\ngogetssl.com true\ngoggs.eu true\ngohon.org true\ngokmenguresci.com true\ngold24.in true\ngold24.ru true\ngoldenhillsoftware.com true\ngoldmark.com.au true\ngoldpreisfinder.at true\ngoldpros.com true\ngoldsecurity.com true\ngolf18network.com true\ngolfscape.com true\ngonzalosanchez.mx true\ngooby.co true\ngoodenough.nz true\ngoodmengroup.de true\ngoogle true\ngooglemail.com false\ngoogleplex.com true\ngopay.cz true\ngordonobrecht.com true\ngorschenin.com true\ngosccs.com true\ngosharewood.com true\ngoshop.cz true\ngospelvestcination.de true\ngostudy.net true\ngosuland.org true\ngotech.com.eg true\ngotgenes.com true\ngothamlimo.com true\ngoto.google.com true\ngotocloud.ru true\ngotomi.info true\ngotspot.com true\ngovernorhub.com true\ngovillemo.ca true\ngovtrack.us true\ngowe.wang false\ngozel.com.tr true\ngpfclan.de true\ngplintegratedit.com true\ngpo.gov false\ngprs.uk.com true\ngps.com.br true\ngpstuner.com true\ngpsvideocanada.com true\ngr.search.yahoo.com false\ngra2.com true\ngraasp.net true\ngraavaapi.elasticbeanstalk.com true\ngrace-wan.com true\ngracedays.org true\ngracesofgrief.com false\ngraciousmay.com true\ngrademypc.com true\ngradienthosting.co.uk true\ngrafitec.ru true\ngrafmurr.de true\ngraingert.co.uk true\ngrandcapital.id true\ngrandcapital.ru true\ngrandlinecsk.ru true\ngrandmasfridge.org true\ngrannyshouse.de true\ngranth.io true\ngranular.ag true\ngraphire.io true\ngrassenberg.de true\ngratisonlinesex.com true\ngravitechthai.com true\ngravito.nl true\ngraycell.net true\ngraymalk.in true\ngrazetech.com true\ngrc.com false\ngrcnode.co.uk true\ngreatfire.org true\ngreatnet.de true\ngreedbutt.com true\ngreenhillantiques.co.uk true\ngreenpeace-magazin.de true\ngreenpeace.berlin true\ngreenroach.ru true\ngreensolid.biz true\ngreenteamtwente.nl true\ngreiners.net true\ngrepular.com true\ngresak.io true\ngresb.com true\ngretchelizartistry.com true\ngreysolutions.it true\ngrh.am true\ngribani.com true\ngrieg-gaarden.no true\ngrieg.com true\ngrieg.no true\ngriegfoundation.no true\ngrieglogistics.no true\ngriegshipbrokers.com true\ngriegshipbrokers.no true\ngriesser2.de true\ngrigalanzsoftware.com true\ngrimm-gastrobedarf.de true\ngrimneko.de true\ngripopgriep.net true\ngritte.ch true\ngrizzlys.com true\ngrocock.me.uk true\ngroetzner.net true\ngrog.pw true\ngrokker.com true\ngroovinads.com true\ngroovydisk.com true\ngropp.org true\ngroseb.net true\ngrossmann.gr true\ngroszek.pl true\ngroupebaillargeon.com true\ngroups.google.com true\ngrsecurity.net true\ngrytics.com true\ngs-net.at true\ngsnort.com true\ngtamodshop.org true\ngtchipsi.org true\ngtldna.com true\ngtmasterclub.it false\ngtmetrix.com true\ngts-schulsoftware.de true\ngudini.net true\nguerrilla.technology true\nguevener.de true\nguffr.it true\ngugaltika-ipb.org true\ngugga.dk true\nguguke.net true\nguidetoiceland.is false\nguillaumeperrin.io true\nguinea-pig.co true\nguineapigmustach.es true\ngulenbase.no true\ngunnaro.com true\nguntbert.net true\ngunwatch.co.uk true\nguoqiang.info true\nguphi.net true\ngurkan.in true\ngurochan.ch true\ngurom.lv true\nguru-naradi.cz true\ngurueffect.com true\ngussi.is true\nguthabenkarten-billiger.de true\nguts.me true\nguvernalternativa.ro true\ngvt2.com true\ngvt3.com true\ngw2treasures.com true\ngwtest.us true\ngycis.me true\ngyz.io true\ngz-architekten.de true\ngz-benz.com true\ngz-bmw.com true\nh-jo.net true\nh404bi.com true\nhaarkliniek.com true\nhabanaavenue.com true\nhabarisoft.com true\nhachre.de false\nhack.cz true\nhackcraft.net true\nhackenkunjeleren.nl true\nhackenturet.dk true\nhacker.one true\nhackerforever.com true\nhackernet.se true\nhackerone-user-content.com true\nhackerone.com true\nhackest.org true\nhackmd.io true\nhackthissite.org true\nhadleighswimmingclub.co.uk true\nhadzic.co true\nhaeckdesign.com true\nhaeckl.eu true\nhafniatimes.com true\nhaircrazy.com true\nhaitschi.com true\nhaitschi.de true\nhaitschi.net true\nhaitschi.org true\nhaktec.de true\nhakugin.org true\nhallelujahsoftware.com true\nhallmarkestates.ca true\nhalo.fr true\nhamali.bg true\nhaman.nl true\nhana.ondemand.com true\nhandiworker.com true\nhandmadetutorials.ro true\nhangouts.google.com true\nhannover-banditen.de true\nhansvaneijsden.com true\nhansvaneijsden.nl true\nhanu.la true\nhaomwei.com true\nhaozi.me true\nhappyandrelaxeddogs.eu true\nhappycoder.net true\nhappygadget.me true\nhappygastro.com true\nhappylifestyle.com true\nhappyteamlabs.com true\nhappytiger.eu true\nhappyukgo.com true\nharabuhouse.com true\nharbor-light.net true\nhardeman.nu true\nhardertimes.com true\nhardfalcon.net true\nhardh.at true\nhardline.xyz true\nharmoney.co.nz true\nharmoney.com true\nharmoney.com.au true\nharmonycosmetic.com true\nharringtonca.com true\nharrisonsand.com false\nharrisonsdirect.co.uk true\nharristony.com true\nhartie95.de true\nhartlep.email true\nharvestapp.com true\nharvester.fr false\nharvestrenewal.org true\nhaselsteiner.me true\nhashiconf.com true\nhashicorp.com true\nhashimah.ca true\nhashnode.com true\nhashplex.com true\nhashru.nl true\nhashworks.net true\nhasinase.de false\nhaskovec.com true\nhastherebeenamassshooting.today true\nhatcherlawgroupnm.com true\nhaucke.xyz true\nhaufschild.de true\nhauntedfishtank.com true\nhausarzt-stader-str.de true\nhausverbrauch.de true\nhaveibeenpwned.com true\nhavellab.de true\nhawkeyeinsight.com true\nhawksguild.com true\nhaxo.nl false\nhazcod.com true\nhboeck.de true\nhbpowell.com true\nhcie.pl true\nhcs-company.com true\nhd-gaming.com true\nhd-offensive.at true\nhda.me true\nhdc.cz true\nhdeaves.uk true\nhdfgroup.org true\nhdhoang.space true\nhdm.io true\nhdrboundless.com true\nhds-lan.de true\nhdsmigrationtool.com true\nhduin.xyz true\nhdwallpapers.net true\nhead.org true\nhealthcare.gov false\nhealtheffectsofasbestos.com true\nhealthfoam.com true\nhealthiercompany.com true\nheartmdinstitute.com true\nheartsucker.com true\nheathmanners.com true\nheavensinferno.net true\nheavystresser.com true\nhebikhiv.nl true\nhec.global true\nhedgeschool.ie true\nhegen.com.pl true\nheh.ee true\nheha.co false\nheid.ws true\nheijblok.com true\nheinpost.nl true\nheissluft-fritteuse.com true\nhejsupport.se true\nhele.cz true\nhelgakristoffer.com true\nhelgakristoffer.wedding true\nhelichat.de true\nhelix.am true\nhellersgas.com true\nhelloacm.com true\nhelloanselm.com true\nhellotandem.com true\nhellscanyonraft.com true\nhelp.simpletax.ca false\nhelpconnect.com.au true\nhelpgoabroad.com true\nhemlockhillscabinrentals.com true\nhencagon.com true\nhennadesigns.org true\nhennymerkel.com true\nhenok.eu true\nhenriksen.is true\nhenrock.net true\nhenryphan.com false\nhentschke-bau.de true\nhentschke-invest.de true\nheppler.net true\nhepteract.us true\nherbandpat.org true\nherbert.io true\nherbertmouwen.nl true\nheritagedentistry.ca true\nherocentral.de true\nherr-webdesign.de true\nherrenfahrt.com true\nherrsmith.com true\nherzbotschaft.de true\nhetmer.com true\nheute-kaufen.de true\nheutger.net true\nhex2013.com true\nhexicurity.com true\nhexo.io true\nhexony.com true\nheycms.com true\nheyguevara.com true\nhg.python.org true\nhhhdb.com true\nhhmmmm.de true\nhibilog.com true\nhicl.org true\nhicoria.com true\nhiddendepth.ie true\nhiexmerida-mailing.com true\nhiggstools.org true\nhighseer.com true\nhighvelocitydesign.com true\nhigp.de true\nhiisukun.com true\nhilahdih.cz true\nhilti.at true\nhilti.ca true\nhilti.cl true\nhilti.co.jp true\nhilti.co.kr true\nhilti.co.uk true\nhilti.com true\nhilti.com.ar true\nhilti.com.au true\nhilti.com.br true\nhilti.com.hk true\nhilti.com.sg true\nhilti.de true\nhilti.ee false\nhilti.es true\nhilti.fi true\nhilti.fr true\nhilti.ie true\nhilti.it true\nhilti.kz false\nhilti.lu true\nhilti.lv false\nhilti.pt true\nhilti.ru true\nhilti.sk true\nhippies.com.br true\nhipstercat.fr true\nhirake55.com true\nhisbrucker.net true\nhistory.google.com false\nhittipps.com true\nhiv.gov true\nhjw-kunstwerk.de true\nhk.search.yahoo.com false\nhlavacek.us true\nhledejpravnika.cz true\nhlfh.space true\nhmsseahawk.com true\nhobby-gamerz-community.de true\nhobbyspeed.com true\nhochhaus.us true\nhochzeit-dana-laurens.de true\nhodne.io true\nhoffmeister.biz true\nholisticon.de true\nhollowrap.com true\nholmesian.org true\nholo.ovh true\nholofono.com true\nholyhiphopdatabase.com true\nholzheizer-forum.de true\nholzheizerforum.de true\nholzvergaser-forum.de true\nhomads.com false\nhomecareassociatespa.com true\nhomedna.com true\nhomehunting.pt true\nhomeprivate.de true\nhomeseller.co.uk true\nhomeseller.com true\nhomewatt.co.uk true\nhomeyou.com true\nhomophoni.com true\nhompus.nl false\nhonda-centrum.cz true\nhondart.cz true\nhoneybadger.io false\nhoneybeard.co.uk true\nhoneycome.net true\nhonoo.com true\nhoodoo.io true\nhoodoo.tech true\nhooowl.com true\nhooray.beer true\nhopesb.org true\nhopewellproperties.co.uk true\nhopps.me true\nhorstmanshof.eu true\nhorvathd.eu true\nhorvathtom.com true\nhorza.org true\nhoshinplan.com true\nhoshisato.com true\nhosiet.me true\nhosmussynergie.nl true\nhostam.link true\nhostanalyticsconsulting.com true\nhosteasy.nl true\nhostedbgp.net true\nhostedtalkgadget.google.com true\nhostelite.com true\nhostingactive.it true\nhostingfj.com true\nhostinghelp.guru true\nhostinginnederland.nl true\nhostix.de true\nhostmijnpagina.nl true\nhotchillibox.co.za true\nhotel-pension-sonnalp.eu true\nhotel-tongruben.de true\nhotelmap.com true\nhotelvictoriaoax-mailing.com true\nhotelvillahermosa-mailing.com true\nhoton.in true\nhotting.nl true\nhousemaadiah.org true\nhouser.lu true\nhousingstudents.org.uk true\nhowbehealthy.com true\nhowsmyssl.com true\nhowsmytls.com true\nhozana.si false\nhpac-portal.com true\nhpisavageforum.com true\nhpkp-faq.de true\nhqhost.net true\nhr-intranet.com true\nhrackydomino.cz true\nhranicka.cz true\nhrbatypes.cz true\nhrobert.hu true\nhroschyk.cz true\nhs-group.net true\nhscorp.de true\nhsmr.cc true\nhsr.gov false\nhstsfail.appspot.com true\nhstspreload.appspot.com true\nhtaccessbook.com true\nhtml5.org true\nhtmlacademy.ru true\nhtmue.org true\nhttp418.xyz true\nhttpsecurityreport.com true\nhttpswatch.com true\nhuagati.com true\nhuang.nu true\nhuaxueba.com true\nhuersch.com true\nhuffduffer.com true\nhugocollignon.fr true\nhugosleep.com.au true\nhuiser.nl true\nhumankode.com true\nhumeurs.net true\nhumpi.at true\nhumpteedumptee.in true\nhund.io true\nhuntshomeinspections.com true\nhup.blue true\nhurd.is true\nhuren.nl true\nhusakbau.at true\nhushfile.it true\nhusky.xyz true\nhuskybutt.dog true\nhuskyinc.us true\nhussam.eu.org true\nhwag-pb.de true\nhx53.de true\nhxying.com true\nhydrocloud.net true\nhydrozone.fr true\nhyk.me true\nhymerscollege.co.uk true\nhypemgmt.com true\nhyper-text.org true\ni--b.com true\ni-jp.net true\ni-stats.net true\ni10z.com false\ni5y.co.uk true\niactu.info true\niainsimms.me true\niamokay.nl true\nian.sh true\nianix.com true\niapws.com true\nib-wedler.de true\niba.community true\nibarf.nl true\nibnuwebhost.com true\nibron.co false\nicarlos.net true\nicebat.dyndns.org true\niceloch.com true\nicfl.com.br true\nichoosebtec.com true\nichronos.net true\nicpc2016.in.th true\nicq-project.net true\nict-concept.nl true\nictinforensics.org true\nictual.com true\nid.atlassian.com true\nid.fedoraproject.org false\nid.mayfirst.org false\nid.search.yahoo.com false\nid0-rsa.pub true\nidahoansforliberty.net true\nidaspis.com true\nideadozz.hu true\nideasmeetingpoint.com true\nideation-inc.co.jp true\nideaweb.de true\nidedr.com true\nidensys.nl true\nidentitytheft.gov true\nidgard.de true\nidgsupply.com true\nidiopolis.org true\nidlekernel.com true\nidndx.com true\nidoc24.com true\nidolf.dk true\nidontexist.me true\nidontplaydarts.com true\nidvl.de true\niec.pe false\nieeespmb.org true\nies-italia.it true\nies.id.lv true\nieval.ro true\nifasec.de false\nifcfg.me true\nifconfig.co true\niflare.de true\nifoss.me true\nifxor.com true\nigforums.com true\niggprivate.com true\niggsoft.com true\niggsoftware.com true\nigiftcards.nl true\nigk.de true\nigotoffer.com true\nigrivi.com true\nihsbsd.me true\niirii.com true\niispeed.com true\nijsclubtilburg.nl true\nikeyless.com true\nikk.me true\nikkatsu-satei.jp true\niklive.org true\nikocik.sk true\nikon.name true\nikvts.de true\nikwilguidobellen.nl true\nilbuongiorno.it true\nile-kalorii.pl true\nilhadocaranguejo.com.br true\nillegalpornography.me true\nillorenese.fr true\nilrg.com true\niltisim.ch true\niluvscotland.co.uk true\nim-c-shop.com true\nim2net.com true\nimagefu.com true\nimagescostumes.com true\nimaginary.ca true\nimagr.io true\nimanolbarba.net true\nimbrian.org true\nimedikament.de true\nimeds.pl true\nimgg.es true\nimguploaden.nl true\nimirhil.fr true\nimjad.cn true\nimjiangtao.com true\nimlonghao.com true\nimmaterium.de true\nimmigrationdirect.com.au true\nimmobilier-nice.fr true\nimmoprotect.ca true\nimmortals-co.com false\nimmoverkauf24.at true\nimmoverkauf24.de true\nimmunicity.eu true\nimmunicity.info true\nimoni-blog.net true\nimperialwebsolutions.com true\nimpex.com.bd true\nimququ.com true\nimreh.net true\nimrejonk.nl true\nimu.li true\nin-depthoutdoors.com true\nin-flames.com true\nin.search.yahoo.com false\nin.xero.com false\ninabox.ro true\ninbitcoin.it true\ninbounder.io true\ninbox-group.com true\ninbox.google.com true\ninboxen.org true\nincendiary-arts.com true\ninche-ali.com true\nincparadise.net true\nindicateurs-flash.fr true\nindiecert.net false\nindievelopment.nl true\nindovinabank.com.vn true\nindredouglas.me true\nindusfastremit-ca.com true\nindusfastremit-us.com true\nindusfastremit.com true\nindustreiler.com true\nindybay.org true\ninfilock.com true\ninfluxus.com false\ninfmed.com true\ninfogrfx.com true\ninformatiebeveiliging.nl true\ninformatik.zone true\ninformnapalm.org true\ninfosec.ch true\ninfosenior.ch true\ninfra-con.dk true\niniiter.com true\ninios.fr true\ninitq.net false\ninitrd.net true\ninjigo.com true\ninkable.com.au true\ninkbunny.net true\ninksay.com true\ninmyarea.com true\ninnophate-security.com true\ninnovaptor.at true\ninnovaptor.com true\ninovatec.com true\ninsane.zone true\ninsideaudit.com true\ninsightera.co.th true\ninsighti.com true\ninsighti.eu true\ninsighti.org true\ninsighti.sk true\ninspire-av.com true\ninspy.me true\ninstant.io true\ninstantdev.io true\ninstasex.ch true\ninstela.com true\ninstitutoflordelavida.com true\ninstitutolancaster.com true\nintarweb.ca true\nintegrationinc.com false\nintegrityingovernmentidaho.com true\nintegromat.com true\nintel.li true\nintelldynamics.com true\nintellectdynamics.com true\ninteraffairs.com true\ninterasistmen.se true\ninterfug.de true\ninterim-cto.de true\ninterisaudit.com true\nintermedinet.nl true\ninternect.co.za true\ninternetbank.swedbank.se true\ninternetbugbounty.org true\ninternetcasinos.de true\ninternetdentalalliance.com true\ninternethering.de true\ninternetofdon.gs true\ninternetpro.me true\ninternetzentrale.net true\ninternl.net false\ninterociter-enterprises.com true\ninterviewpipeline.co.uk true\nintervisteperstrada.com true\ninterways.de true\nintimateperrierjouet.com true\nintocities.de false\ninton.biz true\nintramanager.co.uk true\nintramanager.dk true\nintranetsec.fr true\nintxt.net true\ninusasha.de true\ninvestorforms.com true\ninvictusmc.uk true\ninvoicefinance.nl true\ninwestcorp.se true\ninzdr.com true\niocheck.com false\niodu.re true\niolife.dk true\niompost.com true\niomstamps.com true\nionas-law.ro false\nionc.ca true\nionlabs.kr true\nionx.co.uk true\niosmods.com true\niossifovlab.com true\niotsms.io true\nip-life.net true\nip2country.info true\nip6.li true\nipal.im true\nipal.name true\nipcfg.me true\nipconsulting.se true\niplabs.de true\nipledgeonline.org false\nipmotion.ca true\nipokabu.net true\niprice.co.id true\niprice.hk true\niprice.my true\niprice.ph true\niprice.sg true\niprice.vn true\nipricethailand.com true\niprim.ru true\niprody.com true\nipsec.pl true\nipswitch.com.tw true\niptel.by false\niptel.ro true\nipv6-adresse.dk true\nipv6-handbuch.de true\nipv6cloud.club true\niqboxy.com true\niqcn.co true\nirazimina.ru true\niready.ro true\nireef.tv true\nirelandesign.com true\nirgit.pl true\niridiumbrowser.de true\niridiumflare.de true\nirische-segenswuensche.info true\nirmag.ru true\nironfistdesign.com true\nisaacman.tech true\nisbengrumpy.com true\nisdown.cz true\niseek.biz true\niseulde.com true\nisimonbrown.co.uk true\nisincheck.com true\nisitamor.pm true\nisitchristmas.com true\nisitup.org true\niskaron.de true\nislandhosting.com true\nisletech.net true\nisogen5.com true\nisondo.com true\nisopres.de true\nisqrl.de true\nisrakurort.com true\nisreedyintheuk.com true\nisslshop.com true\nissuesofconcern.in true\nistanbul.systems true\nistanbultravelguide.info true\nistaspirtslietas.lv true\nistdieweltschonuntergegangen.de true\nistheapplestoredown.com true\nistheapplestoredown.de true\nistorrent.is true\nit-go.net true\nit-schwerin.de true\nit.search.yahoo.com false\nitb-online.co.uk true\nitcko.sk true\nitdashboard.gov true\niterror.co true\nitfh.eu true\nitforge.nl true\nitinsight.hu true\nitis4u.ch true\nitludens.com true\nitnota.com true\nitos.asia true\nitos.pl true\nitpol.dk true\nitriskltd.com true\nits-gutachten.de true\nits-v.de true\nits4living.com true\nitsadog.co.uk true\nitsagadget.com true\nitsatrap.nl true\nitsecguy.com true\nitsg-faq.de true\nivancacic.com true\nivk.website true\nivpn.net true\niwalton.com true\niwannarefill.com true\niwilcox.me.uk true\niwizerunek.pl true\niww.mx true\nix8.ru true\nixds.org true\nizoox.com true\nizzzorgconcerten.nl true\nj-lsolutions.com true\nj-navi.com true\nj0s.at true\nj0s.eu true\nj15t98j.co.uk true\nj3e.de true\nja-dyck.de true\nja-publications.com true\njaba.hosting true\njabber.at true\njacekowski.org true\njackdelik.de true\njackf.me true\njackfahnestock.com true\njackyyf.com false\njaclynjohnson.com true\njacobhaug.com true\njacobparry.ca false\njacobphono.com true\njacuzziprozone.com true\njagerman.com true\njagido.de true\njahliveradio.com false\njahofmann.de true\njaimechanaga.com true\njaispirit.com true\njaketremper.com true\njakub-boucek.cz true\njakubboucek.cz true\njakubtopic.cz true\njames.je true\njamesbradach.com true\njamesbywater.co.uk true\njamesbywater.com true\njamesbywater.uk true\njamesdoell.com true\njameshost.net true\njamesmaurer.com true\njamesmilazzo.com true\njamesrains.com true\njamessan.com true\njamielinux.com true\njamiemagee.co.uk true\njamiemagee.dk true\njamon.ca true\njamonsilva.com true\njan-and-maaret.de true\njan-cermak.cz true\njan27.org true\njanario.me true\njanbrodda.de true\njannyrijneveld.nl true\njanoberst.com true\njanosh.com true\njaot.info true\njapan-foods.co.uk true\njapan4you.org true\njapaniac.de true\njaplex.com true\njaredeberle.org true\njaredfernandez.com true\njaroslavtrsek.cz true\njarsater.com true\njasmineconseil.com true\njasonamorrow.com true\njasonrobinson.me true\njasonroe.me true\njasperhammink.com true\njav-collective.com true\njavelinsms.com true\njayharris.ca true\njaymecd.rocks true\njayschulman.com true\njayshao.com true\njazz-alliance.com true\njazz-alliance.org true\njazzanet.com true\njazzinutrecht.info true\njazzncheese.com true\njbbd.fr true\njbn.mx true\njcaicedo.tk true\njcch.de true\njcraft.us true\njctf.io true\njdh8.org true\njdubya.info true\njean-remy.ch true\njeepmafia.com true\njeff.forsale true\njeffcasavant.com false\njekkt.com true\njelly.cz true\njelmer.co.uk true\njelmer.uk true\njennedebleser.com false\njennifersauer.nl true\njennybeaned.com true\njennythebaker.com false\njensrex.dk true\njeremiahbenes.com true\njeremye77.com true\njerodslay.com true\njeroendeneef.com true\njeroenseegers.com true\njessicabenedictus.nl true\njesters-court.net true\njetmirshatri.com true\njetsetcharge.com true\njetsetpay.com true\njetsieswerda.nl true\njettlarue.com true\njetwhiz.com true\njfreitag.de true\njgid.de true\njhalderm.com true\njhburton.co.uk true\njhburton.uk true\njie.dance true\njikken.de true\njimgao.tk true\njimshaver.net true\njinbo123.com false\njira.com true\njirav.com true\njirav.io true\njitsi.org false\njiveiaktivno.bg true\njka.io true\njkirsche.com true\njkrippen.com true\njlkhosting.com true\njmedved.com true\njmk.hu true\njn1.me true\njobbkk.com true\njobflyapp.com true\njobmob.co.il true\njobss.co.uk true\njodyboucher.com true\njoelj.org true\njoepitt.co.uk false\njoerss.at true\njoestead.codes true\njogorama.com.br false\njohannes.wtf true\njohndong.net true\njohngallias.com true\njohnguant.com true\njohnhgaunt.com true\njohnmichel.org true\njohnrom.com true\njohnverkerk.com true\njointoweb.com true\njokewignand.nl true\njonarcher.info true\njonaskjodt.com true\njonaswitmer.ch true\njonathan-apps.com true\njondevin.com true\njonesborostatebank.com true\njonfor.net true\njonlabelle.com true\njonn.me true\njonnichols.info true\njonnybarnes.uk true\njonpads.com true\njoostrijneveld.nl true\njooto.com true\njopsens.de true\njordanhamilton.me true\njoretapo.fr true\njorgemesa.me true\njornadasciberdefensa2016.es true\njosahrens.me true\njosefjanosec.com true\njosephrees.uk true\njoshgrancell.com true\njoshi.su true\njoshtriplett.org true\njoshuarogers.net true\njosip.at false\njotpics.com true\njoworld.net true\njoyofcookingandbaking.com true\njoyqi.com true\njpbike.cz true\njr5devdoug.xyz true\njr5devdouglas.xyz true\njr5proxdoug.xyz true\njrgold.me true\njrmd.io true\njschumacher.info true\njsg-technologies.de true\njson-viewer.com true\njstore.ch true\njthackery.com false\njualssh.com true\njuch.cc true\njugendsuenden.info true\njuhakoho.com true\njulianmeyer.de true\njuliansimioni.com true\njulibear.com true\njulido.de true\njuliemaurel.fr true\njultube.de true\njumba.com.au true\njumbox.xyz true\njump.bg true\njump.wtf true\njunethack.net true\njungesforumkonstanz.de true\njunglist.org true\njuniwalk.cz true\njunjung.me true\njunkdrome.org true\njupp0r.de true\njurriaan.ninja true\njustchunks.net true\njustinlemay.com true\njustnaw.co.uk true\njustyy.com true\njutella.de true\njvoice.net true\njwilsson.com true\njwnotifier.org true\njym.fit true\njznet.org true\nk-tube.com true\nkaangenc.me true\nkaasbijwijn.nl true\nkabat-fans.cz true\nkabuabc.com true\nkabus.org true\nkachlikova2.cz true\nkackscharf.de true\nkadioglumakina.com.tr true\nkaheim.de true\nkahopoon.net true\nkaileymslusser.com true\nkainz.bayern true\nkainz.be true\nkairion.de true\nkaisers.de true\nkaizeronion.com true\nkajak.land true\nkakao-karten.de true\nkakaravaara.fi true\nkalender.com true\nkalevlamps.co.uk true\nkaliaa.fi true\nkall.is true\nkalmar.com true\nkaloix.de true\nkamcvicit.sk true\nkamitech.ch true\nkamixa.se true\nkana.me true\nkandalife.com true\nkanehusky.com true\nkaneo-gmbh.de true\nkangarooislandholidayaccommodation.com.au true\nkaniklani.co.za true\nkanna.cf true\nkanotijd.nl true\nkantankye.nl true\nkantanmt.com true\nkantorosobisty.pl true\nkanzashi.com true\nkapseli.net true\nkapucini.si false\nkaputt.com true\nkarateka.org true\nkarateka.ru true\nkaratorian.org true\nkardize24.pl true\nkarguine.in true\nkarhukamera.com true\nkarmabaker.com true\nkarmaplatform.com true\nkarmaspa.se true\nkarsofsystems.com true\nkarting34.com true\nkartonmodellbau.org true\nkashdash.ca true\nkasko.io true\nkassa.at true\nkatekligys.com true\nkatericke.com true\nkatiaetdavid.fr true\nkatka.info true\nkatnunn.co.uk true\nkatproxy.site true\nkattelans.eu true\nkatyl.info true\nkau-boys.com true\nkau-boys.de true\nkaufberatung.community true\nkavik.no true\nkavovary-kava.cz true\nkawaii.io true\nkazandaemon.ru true\nkb3.net true\nkba-online.de true\nkbcequitas.hu true\nkbit.dk true\nkbjorklu.com true\nkc5mpk.com true\nkcolford.com false\nkd-plus.pp.ua true\nkdex.de true\nkdyby.org true\nke7tlf.us true\nkeaysmillwork.com true\nkedarastudios.com true\nkeeleysam.com true\nkeepa.com true\nkeeperapp.com true\nkeepersecurity.com true\nkeeweb.info true\nkefaloniatoday.com true\nkeganthorrez.com true\nkeifel.de true\nkeisaku.org true\nkeithws.net true\nkeke-shop.ch true\nkekku.li true\nkekz.org true\nkempkens.io true\nkendra.io true\nkendu.si true\nkengilmour.com true\nkenkoelectric.com true\nkenners.org true\nkentacademiestrust.org.uk true\nkeops-spine.fr true\nkeops-spine.us true\nkerangalam.com true\nkermadec.com true\nkernel-error.de true\nkerrfrequencycombs.org true\nkeskeces.com true\nkesteren.com true\nkesteren.org true\nketosecology.co.uk true\nkevinapease.com true\nkevinbusse.de true\nkevincox.ca true\nkevindekoninck.com true\nkeybase.io true\nkeybored.me true\nkeycdn.com true\nkeyerror.com true\nkeys.fedoraproject.org true\nkg-rating.com true\nkgm-irm.be true\nkhanovaskola.cz true\nkhetzal.info true\nkhipu.com true\nkhmath.com true\nkhmb.ru true\nki-on.net true\nkiano.net true\nkick-in.nl true\nkickerplaza.nl true\nkid-dachau.de true\nkidbacker.com true\nkiebel.de true\nkiel-media.de true\nkienlen.org true\nkikuzuki.org true\nkilerd.me true\nkilianvalkhof.com true\nkillerrobots.com true\nkilobyte22.de true\nkimberg.co.uk true\nkimmel.com true\nkimmel.in true\nkinderbasar-luhe.de true\nkinderbuecher-kostenlos.de true\nkinderwagen-test24.de true\nkindleworth.com true\nkindlyfire.com true\nkindof.ninja true\nkingant.net true\nkinganywhere.eu true\nkingqueen.org.uk true\nkinkdr.com true\nkinkenonline.com true\nkinsights.com false\nkintone.com true\nkintore.tv true\nkiocloud.com true\nkionetworks.com true\nkirara.eu true\nkirbear.com true\nkirei.se true\nkirinas.com true\nkirkovsky.com true\nkirsch-gestaltung.de true\nkirschbaum.me true\nkirstin-peters.de true\nkis-toitoidixi.de true\nkisalt.im true\nkissflow.com true\nkisstyle.ru true\nkita.id true\nkitabgaul.com true\nkitsostech.com true\nkittmedia.com true\nkjaermaxi.me true\nkjarrval.is true\nkjchernov.info true\nkk-neudorf-duissern.de true\nkksg.com true\nklares-licht.de true\nklarmobil-empfehlen.de true\nklas.or.id true\nklasfauseweh.de true\nklatschreime.de true\nklausbrinch.dk false\nklausimas.lt true\nklauwd.com true\nkle.cz true\nkleertjesvoordelig.nl true\nkleidertauschpartys.de true\nkleine-dingen.nl true\nkleinerarchitekturfuehrer.de true\nkleppe.co true\nkleteckova.cz true\nkletterkater.com true\nkliemann.me true\nklif1.nl true\nklimat-pro.pl true\nklingeletest.de true\nklunkergarten.org true\nklustekeningen.nl true\nkm-net.pl true\nkmkz.jp true\nkn007.net true\nknapen.io true\nknccloud.com true\nkngk-group.ru true\nkngk.org true\nknip.ch true\nknot-store.com true\nknowledgehook.com true\nknowledgesnap.com true\nknygos.lt true\nkodakit.com true\nkoebbes.de true\nkoen.io true\nkoenrouwhorst.nl true\nkoenvdheuvel.me false\nkoerper-wie-seele.de false\nkoerperimpuls.ch true\nkoezmangal.ch true\nkoi-sama.net true\nkojima-life.co.jp true\nkojipkgs.fedoraproject.org true\nkokenmetaanbiedingen.nl true\nkoketteriet.se true\nkollawat.me true\nkolmann.at true\nkolmann.eu true\nkomget.net true\nkomiksbaza.pl true\nkomoju.com true\nkompetenzwerft.de true\nkon-sil.de true\nkonata.us true\nkoniecfica.sk true\nkonijntjes.nl true\nkonklone.com true\nkonsertoversikt.no true\nkoophetlokaal.nl true\nkoopjesnel.nl true\nkoordinate.net true\nkoot.nl true\nkopfsalat.eu true\nkopular.com true\nkoretech.nl true\nkorinar.com true\nkornersafe.com true\nkorobi.io true\nkorobkovsky.ru true\nkorrelzout.nl true\nkorsanparti.org true\nkosho.org true\nkostya.net false\nkotonehoko.net true\nkoukni.cz true\nkozuch.biz true\nkpdyer.com true\nkpebetka.net true\nkpinvest.eu true\nkrachtinverbinding.nl true\nkradalby.no true\nkraft.im true\nkraiwan.com true\nkraiwon.com true\nkraken.io true\nkrasota.ru false\nkraynik.com true\nkream.io true\nkreationnext.com true\nkreativstrecke.de true\nkreavis.com true\nkreb.io true\nkredietpaspoort.nl true\nkredite24.de true\nkreen.org true\nkriegskindernothilfe.de true\nkriegt.es true\nkrislamoureux.com true\nkrisstarkey.co.uk true\nkristikala.nl true\nkristofferkoch.com true\nkrizek.cc true\nkrk-media.pl true\nkrmela.com true\nkrmeni.cz true\nkroetenfuchs.de true\nkromonos.net true\nkroodle.nl true\nkroon.email true\nkropkait.pl true\nkrunut.com true\nkrypsys.com true\nkrypteia.org true\nkryptomech.com true\nkryx.de true\nks-watch.de true\nkschv-rdeck.de true\nksfh-mail.de true\nkstan.me true\nkubik-rubik.de false\nkucom.it true\nkuehnel.org true\nkuemmling.eu true\nkulde.net true\nkum.com true\nkumachan.biz true\nkumasanda.jp true\nkummerlaender.eu true\nkunstundunrat.de true\nkupelne-ptacek.sk true\nkuponrazzi.com true\nkupschke.net true\nkura.io false\nkurtmclester.com true\nkuschelmikroben.de true\nkuwago.io true\nkvalita-1a.cz true\nkwbresidential.com true\nkwidz.fr true\nkwikmed.eu true\nkwok.cc true\nkyanite.co true\nkybi.sk true\nkylapps.com true\nkylelaker.com true\nkylling.io true\nkynastonwedding.co.uk true\nkyosaku.org true\nkzsdabas.hu true\nl-lab.org true\nl4n-clan.de true\nlabrador-retrievers.com.au true\nlabradorpuppiesforsalebyregisteredlabradorbreeders.com true\nlabs.directory true\nlabs.moscow true\nlacasabelucci.com true\nlacasseroy.com true\nlacentral.com false\nlacledeslan.com true\nlacledeslan.ninja true\nlafkor.de true\nlagalerievirtuelle.fr true\nlagarderob.ru true\nlagerauftrag.info true\nlaglab.org true\nlainchan.org true\nlak-berlin.de true\nlalaya.fr true\nlamaland.ru true\nlambda-complex.org true\nlamboo.be true\nlaminine.info true\nlan2k.org true\nlana.swedbank.se true\nlanbyte.se true\nlancejames.com true\nlancork.net true\nland.nrw true\nlangatang.com true\nlangbein.org true\nlangenbach.rocks true\nlangendries.eu true\nlangguth.io true\nlanghun.me true\nlansinoh.co.uk true\nlaozhu.me true\nlapetition.be true\nlaposte.net true\nlargescaleforums.com true\nlarrysalibra.com true\nlars-ewald.com true\nlarsklene.nl true\nlasercloud.ml true\nlaserfuchs.de true\nlashstuff.com true\nlasnaves.com true\nlasst-uns-beten.de true\nlastpass.com false\nlatenitefilms.com false\nlateralsecurity.com true\nlatinphone.com true\nlatitude42technology.com true\nlatrine.cz true\nlauftreff-himmelgeist.de true\nlaukstein.com true\nlaunchkey.com true\nlaurel4th.org true\nlausitzer-widerstand.de true\nlavalite.de true\nlaventainnhotel-mailing.com true\nlavine.ch true\nlavinya.net true\nlavoiepharmd.com true\nlavval.com true\nlawformt.com true\nlaylo.nl true\nlazurit.com true\nlb-toner.de true\nlbayer.com true\nlbrt.xyz true\nlcti.biz true\nld-begunjscica.si true\nle-dev.de true\nle-h.de true\nle-hosting.de true\nleadbook.ru true\nleadership9.com true\nleadingsalons.com true\nleakedminecraft.net true\nleanclub.org true\nleardev.de true\nlearnflakes.net true\nlearningorder.com true\nlearntube.cz true\nleatherfurnitureexpo.com true\nleaversmith.com true\nlechiennoir.net true\nledgerscope.net false\nledhouse.sk true\nleedev.org true\nleerliga.de true\nleesilvey.com true\nlega-dental.com true\nlegendofkrystal.com true\nlegoutdesplantes.be true\nlehighmathcircle.org true\nleibniz-remscheid.de true\nleilonorte.com true\nlemoine.at true\nlemp.io true\nlence.net true\nlengzzz.com true\nleninalbertop.com.ve true\nlenn1.de true\nlenovogaming.com true\nlenzw.de true\nleominstercu.com false\nleonard.io true\nleonardcamacho.me true\nleonax.net true\nleonklingele.de true\nleonmahler.consulting true\nlepont.pl true\nleppis-it.de true\nlerner.moscow true\nlerp.me true\nlesdouceursdeliyana.com true\nlesharris.com true\nlesnet.co.uk true\nlesperlesdunet.fr true\nletsmultiplayerplay.com true\nletstox.com true\nletustravel.tk true\nlevendwater.org true\nlevert.ch true\nlevinus.de true\nlewis.li true\nlewislaw.com true\nlexway.pk true\nlgiswa.com.au true\nlgrs.com.au true\nlhalbert.xyz true\nliam-w.com true\nliamjack.fr true\nlibbitcoin.org true\nlibertas-tech.com true\nlibfte.org true\nlibraryfreedomproject.org false\nlibreboot.org true\nlibrelamp.com true\nlibscode.com true\nlibsodium.org true\nliceserv.com true\nlichtspot.de true\nliderwalut.pl true\nliebel.org true\nlife-time.nl true\nlifeinitsownway.com false\nlifeskillsdirect.com true\nlifestylehunter.co.uk true\nlifetimemoneymachine.com true\nlifi.digital true\nlifi.is true\nlight.mail.ru true\nlighting-centres.co.uk true\nlightme.us true\nlightspeed.com false\nlillepuu.com true\nlilpwny.com true\nlimeyeti.com true\nlimitededitioncomputers.com true\nlimitededitionsolutions.com true\nlimpid.nl true\nlimpido.it true\nlingolia.com true\nlingros-test.tk true\nlinguatrip.com false\nlingvo-svoboda.ru true\nlink.ba true\nlink2serve.com true\nlinkages.org true\nlinkenheil.org true\nlinkmaker.co.uk true\nlinkonaut.net true\nlinno.me true\nlinode.com false\nlinorman1997.me true\nlinpx.com true\nlinux-admin-california.com true\nlinux.cn true\nlinux.fi true\nlinuxbabe.com true\nlinuxbierwanderung.com true\nlinuxcommand.ru true\nlinuxfixed.it true\nlinuxhostsupport.com true\nlinuxlounge.net true\nlinuxmonitoring.net true\nlinx.li true\nlinx.net true\nlinzgau.de true\nliornavok.com true\nliquid.cz true\nliquidcomm.net true\nliris-beautywelt.de true\nlisaco.de true\nlislan.org.uk true\nlister-kirchweg.de true\nlists.fedoraproject.org true\nlists.mayfirst.org false\nlists.stg.fedoraproject.org true\nlitchidova.nl true\nlitespeed.io true\nlitevault.net true\nlithesalar.se true\nlittle.pw true\nlittlefreelibrary.org true\nlitvideoserver.de true\nliud.im true\nliudon.org true\nlivecards.co.uk true\nliveforspeed.se true\nlivekaarten.be true\nlivekaarten.nl true\nlivekort.dk true\nlivekort.se true\nliverewrite.com true\nliverpoolmutualhomes.org false\nlivnev.me true\nljs.io true\nlkummer.cz true\nllamacuba.com true\nlloyd-day.me true\nlm-pumpen.de true\nlmddgtfy.net true\nlmintlcx.com true\nlmmtfy.io true\nlmsptfy.com true\nlnoldan.com true\nlntu.org true\nloadso.me true\nloancompare.co.za true\nlobste.rs true\nlocalbandz.com true\nlocalbitcoins.com true\nlocalchum.com true\nlockify.com true\nlocktheirphone.com true\nlocomore.com true\nlocomotive.ca true\nlodash.com true\nlogario.com.br true\nlogement-saisonnier.com true\nlogentries.com false\nlogfile.at true\nlogicsale.com true\nlogicsale.de true\nlogicsale.fr true\nlogicsale.it true\nlogin.corp.google.com true\nlogin.gov true\nlogin.launchpad.net true\nlogin.persona.org true\nlogin.sapo.pt true\nlogin.ubuntu.com true\nlogin.xero.com false\nlogin.yahoo.com false\nloginseite.com true\nlogistify.com.mx true\nlognot.net true\nlogopaediereinhard.de true\nlogopoeia.com true\nlokaal.org true\nloli.bz true\nlolicore.ch true\nlolpatrol.de true\nlonal.com true\nlondon-transfers.com true\nlondonlanguageexchange.com true\nlone-gunman.be true\nlongboarding-ulm.de true\nlookout.com false\nlookyman.net true\nloopstart.org true\nlordjevington.co.uk true\nlore.azurewebsites.net true\nlost.host false\nlostinsecurity.com true\nlostinweb.eu true\nlotsencafe.de true\nlottosonline.com true\nloucanfixit.com true\nlouiewatch.com true\nlove-schna.jp true\nloveisourweapon.com true\nlovemomiji.com true\nlover-bg.com true\nloveyounastya.com true\nlovingearth.co true\nlovingearth.net true\nlowmagnitude.com true\nlowsidetna.com true\nlpbk-bethel.de true\nlpm-uk.com true\nlrhstsa.com true\nls-a.org true\nltn-tom-morel.fr true\nlubot.net false\nlucaterzini.com true\nluchscheider.de true\nlucidframeworks.com true\nlucielavickova.com true\nludwig.im true\nluehne.de true\nluelistan.net true\nlufthansaexperts.com true\nlugbb.org true\nluine.xyz true\nluis-checa.com true\nlukasberan.cz true\nlukasunger.cz true\nlukasunger.net true\nlukaszdolan.com true\nlukasztkacz.com true\nluke.ch true\nlumiere.com true\nlunakit.org true\nlunarift.com true\nlunarshark.com true\nluneta.nearbuysystems.com false\nlungdoc.us true\nluno.io true\nluoh.cc true\nluoh.me true\nluohua.im true\nluom.net true\nlustrum.ch true\nluther.fi true\nluukklene.nl true\nluxsci.com true\nluxwatch.com true\nlv0.it true\nlvrsystems.com true\nlwl-foej-bewerbung.de true\nlwl.moe true\nlymia.moe true\nlynero.dk true\nlyness.io true\nlynkos.com true\nlynthium.com true\nlyst.co.uk true\nlyx.dk true\nlzkill.com true\nlzzr.me true\nm-edmondson.co.uk true\nm.facebook.com false\nm.mail.ru true\nm.nu true\nm0wef.uk true\nm82labs.com true\nma2t.com true\nmac-torrents.me true\nmacaque.io false\nmacchaberrycream.com true\nmacgeneral.de true\nmach-politik.ch true\nmachbach.com true\nmachon.biz false\nmacinyasha.net true\nmaclemon.at true\nmacleod.io true\nmacnemo.de true\nmaco.org.uk true\nmadars.org true\nmaddi.biz true\nmadebymagnitude.com true\nmadebyshore.com true\nmadeitwor.se true\nmadin.ru true\nmadrants.net true\nmadreacqua.org true\nmadtec.de true\nmaelstrom.ninja true\nmaff.scot false\nmafiasi.de true\nmagicball.co true\nmagneticanvil.com true\nmahefa.co.uk true\nmahrer.net true\nmail-settings.google.com true\nmail.de true\nmail.google.com true\nmail.yahoo.com false\nmail4you.in true\nmailbox.org true\nmailchuck.com true\nmailfence.com true\nmailgarant.nl true\nmailhost.it true\nmailinabox.email true\nmailing-jbgg.com true\nmailmag.net true\nmainlined.org true\nmainlywrenches.co true\nmake-pizza.info true\nmakeyourlaws.org true\nmakowitz.cz true\nmaktoob.search.yahoo.com false\nmalash.me true\nmalaysia.search.yahoo.com false\nmalfait.nl true\nmalinator.net true\nmaliskovik.si true\nmall.cz true\nmall.hr true\nmall.hu true\nmall.pl true\nmall.sk true\nmalnex.de true\nmalware.watch true\nmalwaretips.com true\nmamaxi.org true\nmammaw.com true\nmammothmail.com true\nmammothmail.net true\nmammothmail.org true\nman3s.jp true\nmanage.cm true\nmanage.zenpayroll.com false\nmanagement-companie.ro true\nmanagement-ethics.com true\nmanagemynetsuite.com true\nmanageprojects.com true\nmanager.linode.com false\nmanagewp.org true\nmandala-ausmalbilder.de true\nmanfredimatteo.com true\nmanhattanchoralensemble.org true\nmanicode.com true\nmanifestbin.com true\nmanningbrothers.com true\nmanoirdecontres.com true\nmanowarus.com true\nmansfieldplacevt.com true\nmantor.org false\nmanueli.de true\nmaosensanguentadasdejesus.net true\nmaowtm.org true\nmarbogardenlidkoping.se true\nmarcel-preuss.de true\nmarcelpreuss.de true\nmarcoececilia.it true\nmarcofinke.de true\nmarcohager.de true\nmarcontrol.com true\nmareklecian.cz true\nmariannematthew.com true\nmariaolesen.dk true\nmarie-curie.fr true\nmarie-elisabeth.dk true\nmarie.club true\nmarikafranke.de true\nmarilsnijders.nl true\nmarinelausa.com true\nmarines-shop.com true\nmark-a-hydrant.com true\nmark-semmler.de true\nmarkaconnor.com true\nmarket.android.com true\nmarketingdesignu.cz true\nmarkhaehnel.de false\nmarkido.com true\nmarkoh.co.uk true\nmarkom.rs true\nmarkprof.ru true\nmarkri.nl true\nmarkt-heiligenstadt.de true\nmarktboten.de true\nmarktcontact.com true\nmarkusehrlicher.de true\nmarkusueberallassetmanagement.de true\nmarlen.cz true\nmarquiseclub.se true\nmarriottvetcareers.com true\nmarsble.com true\nmartelange.ovh true\nmartensmxservice.nl true\nmartialc.be true\nmartiert.com true\nmartin-smith.info true\nmartineve.com true\nmartinkup.cz true\nmartinkus.eu true\nmartinp.no true\nmartinsfamilyappliance.com true\nmarumagic.com true\nmasa-yoga.com true\nmascosolutions.com true\nmassdrop.com true\nmasse.org true\nmassivum.de false\nmastellone.us true\nmasteringtheterminal.com true\nmasters.black true\nmatatall.com false\nmatchneedle.com true\nmaternalsafety.org true\nmathembedded.com true\nmathhire.org true\nmathiasbynens.be true\nmathiasgarbe.de true\nmatt.tf true\nmattandreko.com true\nmattandyana.com true\nmattberryman.com false\nmatteomarescotti.it true\nmattfin.ch true\nmatthewohare.com true\nmatthiasadler.info true\nmatthiasschwab.de true\nmatthiassteen.be true\nmattia98.org true\nmattmccutchen.net true\nmattsvensson.com true\nmattwb65.com true\nmauriciog.com.ar true\nmauricioghiorzi.com.ar true\nmausi.co true\nmavenclinic.com true\nmavensecurity.com true\nmaveris.com true\nmax-moeglich.de true\nmax.gov true\nmaxbytes.nl true\nmaxima.at true\nmaximdeboiserie.be true\nmaximeferon.fr true\nmaximelouet.me true\nmaximilian-greger.com true\nmaximiliankrieg.de true\nmaxmilton.com true\nmaxr1998.de true\nmaxserver.com true\nmaxtruxa.com true\nmaxwell-english.co.jp false\nmaya.mg true\nmbaestlein.de true\nmbasic.facebook.com false\nmbilker.us true\nmbinf.de true\nmbinformatik.de true\nmbp.banking.co.at false\nmbweir.com true\nmc-venture.net false\nmc81.com true\nmcadmin.net true\nmcard.vn true\nmcatnnlo.org true\nmcc.re true\nmccarty.io true\nmccrackon.com true\nmcdonalds.be true\nmcdonaldwhsl.com true\nmce.eu true\nmce.nyc true\nmce55.eu true\nmceconferencecentre.eu true\nmcgarderen.nl true\nmchristopher.com true\nmcl.gg true\nmclab.su false\nmcnext.net true\nmcooperlaw.com true\nmcpart.land true\nmcrn.jp true\nmctherealm.net true\nmcuong.tk true\nmd5file.com true\nmd5hashing.net true\nmdek.at true\nmdewendt.de true\nmdkr.nl true\nmdpraha.cz true\nmdwftw.com true\nme.net.nz true\nmea.in.ua true\nmealgoo.com true\nmeamod.com true\nmeap.xyz true\nmebio.us false\nmechanixdirect.co.uk true\nmechanus.io true\nmedba.se true\nmeddelare.com true\nmedexpress.co.uk true\nmedia-courses.com true\nmediaburst.co.uk true\nmediaselection.eu true\nmediawiki.org true\nmedicinesfast.com true\nmedirich.co true\nmeditek-dv.ru true\nmedium.com true\nmedm-test.com true\nmedo64.com true\nmedovea.ru true\nmedtehnika.ua true\nmeedoenhartvanwestbrabant.nl true\nmeedoenzaanstad.nl true\nmeetbot.fedoraproject.org true\nmeetfinch.com true\nmeetingmanage.nl true\nmeetingmanager.ovh true\nmeetmibaby.co.uk true\nmeetscompany.jp true\nmega.co.nz true\nmega.nz true\nmega.online true\nmegadrol.com true\nmegakiste.de true\nmegaplan.cz true\nmegaplan.ru true\nmegasslstore.com true\nmehmetince.net true\nmehrwert.de true\nmeillard-auto-ecole.ch true\nmein-webportal.de true\nmeine-email-im.net true\nmeizufans.eu true\nmelcher.it true\nmelearning.university true\nmelf.nl true\nmelissaadkins.com true\nmelitopol.co.ua true\nmelnikov.ch true\nmelody-lyrics.com true\nmelted.pw true\nmelvinlow.com true\nmemberpress.com true\nmembers.nearlyfreespeech.net false\nmemo-linux.com true\nmensagemdaluz.com true\nmentax.net false\nmentiq.az true\nmentz.info true\nmenudrivetest.com true\nmenuonlineordering.com true\nmeozcraft.com false\nmercamaris.es true\nmerccorp.de true\nmercuryamericas.com false\nmercurystorm.co.za true\nmereckas.com true\nmeredithkm.info true\nmerkel.me true\nmersinunivercity.com true\nmertcangokgoz.com true\nmesmoque.com true\nmesvt.com true\nmeta-db.com true\nmeta.sc true\nmetaether.net true\nmetapeen.nl true\nmeteobox.co true\nmeteobox.cz true\nmeteobox.de true\nmeteobox.es true\nmeteobox.fr true\nmeteobox.mx true\nmeteobox.pl true\nmeteobox.sk true\nmeteorapp.space true\nmeteosherbrooke.com true\nmeteosky.net true\nmetis.pw true\nmetrobriefs.com true\nmetzgerei-birkenhof.de true\nmeuemail.pro true\nmevs.cz true\nmexicansbook.ru false\nmeyeraviation.com true\nmfcatalin.com true\nmfiles.pl true\nmgdigital.fr true\nmghiorzi.com.ar true\nmh-bloemen.co.jp true\nmheistermann.de true\nmhertel.com false\nmhx.pw true\nmiagexport.com true\nmiasarafina.de true\nmichael-rigart.be true\nmichaelcullen.name true\nmichaelleibundgut.com true\nmichaelmorpurgo.com true\nmichaelrigart.be true\nmichaeltroger.com true\nmichaelwaite.org true\nmichalkral.tk true\nmichalspacek.cz true\nmichasfahrschule.com true\nmichel-wein.de true\nmichiganunionoptout.com true\nmiconcinemas.com true\nmiconware.de true\nmicro-rain-systems.com true\nmicrodots.de true\nmicrome.ga true\nmicrovb.com true\nmidair.io true\nmidirs.org true\nmidlgx.com true\nmidonet.org true\nmig5.net true\nmightysounds.cz true\nmijailovic.net true\nmijcorijneveld.nl true\nmijnkredietpaspoort.nl true\nmijnreisoverzicht.nl true\nmijntransacties.nl true\nmikadesign.se false\nmikadoe.nl true\nmikaela.info true\nmikaelemilsson.net true\nmike-bland.com true\nmikeburns.com true\nmiketabor.com true\nmikewest.org true\nmikewillia.ms true\nmikeybot.com true\nmiku.be true\nmil0.com true\nmilahendri.com false\nmilanpala.cz true\nmilatrans.pl true\nmillenniumweb.com true\nmillistream.com true\nmim.properties true\nmimeit.de true\nmimovrste.com true\nmind-moves.es true\nmindbodycontinuum.com true\nminecraft-forum.cf true\nminecraft-forum.ga true\nminecraft-forum.gq true\nminecraft-forum.ml true\nminecraft-forums.cf true\nminecraft-forums.ga true\nminecraft-forums.gq true\nminecraftforum.de true\nminecraftforum.ovh true\nminecraftforums.cf true\nminecraftforums.gq true\nminecraftforums.ml true\nmineover.es true\nminesouls.fr true\nminez-nightswatch.com false\nmini2.fi true\nminikneet.com true\nminipainting.net true\nminis-hip.de true\nminiskipper.at true\nminkondom.nu true\nminobar.com true\nminora.io true\nminpingvin.dk true\nmintea-noua.ro true\nmintrak2.com true\nmipiaci.co.nz true\nmipiaci.com.au true\nmireservaonline.es true\nmirodasilva.be true\nmironet.cz true\nmirtes.cz true\nmisakiya.co.jp true\nmiskatonic.org true\nmiss-inventory.co.uk true\nmissdream.org true\nmissrain.tw true\nmistacms.com true\nmister.hosting true\nmisterl.net true\nmit-uns.org true\nmitnetz-gas.de true\nmitnetz-strom.de true\nmitrecaasd.org true\nmitremai.org true\nmittelunsachlich.de true\nmitzpettel.com true\nmixposure.com true\nmiyoshi-kikaku.com true\nmjacobson.net true\nmjanja.ch true\nmjcaffarattilaw.com true\nmjec.net true\nmkcert.org true\nmkes.com false\nmkp-deutschland.de true\nmktemp.org true\nmkw.st true\nmlemay.com true\nmlp.ee true\nmlpepilepsy.org true\nmlsrv.de true\nmm13.at true\nmm404.com true\nmma-acareporting.com true\nmmgazhomeloans.com true\nmmmm.com true\nmmonit.com true\nmmucha.de true\nmnd.sc true\nmnetworkingsolutions.co.uk true\nmnium.de true\nmnmt.no true\nmnsure.org true\nmoar.so true\nmobaircon.com true\nmobal.com true\nmobidea.com true\nmobilcom-debitel-empfehlen.de true\nmobile.eti.br true\nmobile.united.com false\nmobile.usaa.com false\nmobilekey.co true\nmobilemedics.com true\nmobiletraff.co true\nmobility-events.ch true\nmobilpass.no true\nmobilux.lv true\nmobobe.com true\nmocloud.eu true\nmodelcase.co.jp true\nmodeldimension.com true\nmodernapprenticeships.org true\nmodernibytovytextil.cz true\nmodifiedmind.com true\nmodmountain.com true\nmodydev.club true\nmoebel-nagel.de true\nmoegirl.org true\nmoen.io true\nmogry.net true\nmojaknjiznica.com false\nmojapraca.sk true\nmokote.com true\nmommel.com true\nmommelonline.de true\nmon-partage.fr true\nmona.lu true\nmondopoint.com true\nmoneromerchant.com true\nmoneygo.se true\nmoniquedekermadec.com true\nmonitaure.io true\nmonitman.solutions true\nmonitzer.com true\nmonix.io true\nmonkeydust.net true\nmonnyonle.hu true\nmonobank.no true\nmontanacures.org true\nmontenero.pl true\nmontonicms.com true\nmoo.la true\nmoon.lc true\nmoonagic.com true\nmoonloupe.com true\nmoonraptor.co.uk true\nmoonraptor.com true\nmoonvpn.org true\nmoparcraft.com true\nmoparcraft.net true\nmoparcraft.org true\nmoparisthebest.com true\nmoparisthebest.net true\nmoparisthebest.org true\nmoparscape.org true\nmor.cloud true\nmorbatex.com true\nmorbitzer.de true\nmoreapp.co.uk true\nmorenci.ch true\nmorethanadream.lv true\nmorganino.eu true\nmorganino.it true\nmoritz-baestlein.de true\nmoriz.de true\nmorningcalculation.com true\nmornings.com true\nmorteruelo.net true\nmorz.org true\nmosstier.com true\nmostwuat.com true\nmotd.ch true\nmotherbase.io true\nmothereff.in true\nmotionfreight.com true\nmotocyklovedily.cz true\nmotoryz.nl true\nmountainadventureseminars.com true\nmountainroseherbs.com true\nmoveek.com true\nmoviedollars.com true\nmovinglogistics.nl true\nmovlib.org true\nmoy-gorod.od.ua true\nmozoa.net true\nmp3gratuiti.com true\nmpc-hc.org true\nmpcompliance.com true\nmpetroff.net true\nmpintaamalabanna.it true\nmplant.io true\nmplusm.eu true\nmpreserver.com true\nmpserver12.org true\nmpsgarage.com.au true\nmr-hosting.com true\nmremallin.ca true\nmrettich.org true\nmrnonz.com true\nmrs-shop.com true\nmsa-aesch.ch true\nmsebera.cz true\nmsiegmund.com true\nmsmails.de true\nmsno.no true\nmszaki.com true\nmt.me.uk true\nmtasa.com true\nmtau.com true\nmtg-tutor.de true\nmthode.org true\nmths.be true\nmtn.cc true\nmtnz.co.za true\nmtouch.facebook.com false\nmuabannhanh.com false\nmudcrab.us true\nmuguayuan.com true\nmujadin.se false\nmultibit.org true\nmultigamecard.com true\nmultigeist.de true\nmultitheftauto.com true\nmumei.space true\nmunich-rage.de true\nmunki.org true\nmunuc.org true\nmurfy.nz true\nmuriburi.land true\nmuriburiland.com true\nmurodese.org true\nmurraycoin.org true\nmuscleangels.com true\nmushikabu.net true\nmusi.cx true\nmusicgamegalaxy.de true\nmusicwear.cz true\nmusikkfondene.no true\nmusmann.io true\nmusthavesforreal.com true\nmutantmonkey.in true\nmutantmonkey.info true\nmutantmonkey.sexy true\nmutuelle.fr true\nmuzykaprzeszladoplay.pl true\nmvanmarketing.nl true\nmvixturismo.com.br true\nmvno.io true\nmwavuli.co.ke true\nmwe.st true\nmx.org.ua false\nmx.search.yahoo.com false\nmy-owncloud.com true\nmy-pawnshop.com.ua true\nmy.onlime.ch false\nmy.usa.gov false\nmy.xero.com false\nmyaccount.google.com true\nmyairshop.gr true\nmybeautyjobs.de true\nmybon.at true\nmybudget.xyz true\nmybuilderinlondon.co.uk true\nmycieokien.info false\nmyclientsplus.com true\nmyconan.net true\nmydnaresults.com true\nmydnatest.com true\nmydocserve.com true\nmyg21.com true\nmygadgetguardian.lookout.com false\nmygate.at false\nmygreatjobs.de true\nmygretchen.de true\nmyhostname.net true\nmyiocc.org true\nmyip.tech true\nmykeepsake.xyz true\nmykontool.de true\nmylookout.com false\nmynigma.org true\nmyonline.hu true\nmyownconference.com true\nmyownconference.com.ua true\nmyownconference.de true\nmyownconference.es true\nmyownconference.fr true\nmyownconference.lt true\nmyownconference.lv true\nmyownconference.pl true\nmyownconference.ru true\nmypagella.com true\nmypagella.eu true\nmypagella.it true\nmypayoffloan.com true\nmyphonebox.de true\nmyprintcard.de true\nmyraytech.net false\nmyruststats.com true\nmysecretcase.com true\nmyshirtsize.com true\nmysmelly.com true\nmysoundtalks.com true\nmysticplumes.com true\nmytc.fr true\nmytripcar.co.uk true\nmytripcar.com true\nmytripcar.de true\nmytripcar.es true\nmytty.net true\nmytweeps.com true\nmyvpl.com true\nmyworkinfo.com true\nmyworth.com.au true\nmyzina.cz false\nmziulu.me true\nn-pix.com false\nn0psled.nl true\nn4l.pw true\nnabankco.com true\nnabru.co.uk true\nnabytko.cz true\nnachsendeauftrag.net true\nnachsenden.info true\nnafod.net true\nnagaya.biz true\nnagb.gov true\nnagb.org true\nnagelfam.com true\nnagoya-kyuyo.com true\nnakedalarmclock.me true\nnako.no true\nnalao-company.com true\nnamacindia.com true\nnamegrep.com true\nnameid.org true\nnamepros.com true\nnametiles.co true\nnaminam.de true\nnamorico.me false\nnamu.wiki true\nnan.zone true\nnanderson.me true\nnanogi.ga true\nnansay.cn true\nnanto.eu true\nnarach.com true\nnarada.com.ua true\nnarfation.org true\nnargele.eu true\nnargileh.nl true\nnaro.se true\nnarodniki.com true\nnarthollis.net true\nnartuk.com.ua false\nnatalt.org true\nnatanaelys.com true\nnatenom.com true\nnatenom.name true\nnathancheek.com false\nnathanmfarrugia.com true\nnathansmetana.com true\nnationalcentereg.org true\nnationalpriorities.org true\nnatural-progesterone.net true\nnaturesystems.cz true\nnav.jobs true\nnavjobs.com true\nnavstevnik.sk true\nnavycs.com true\nnayahe.ru true\nnb.zone true\nnb6.de true\nnbl.org.tw true\nnbp.com.pk true\nncc60205.info true\nncoding.at true\nncpc.gov true\nncpw.gov true\nncrmnt.org true\nncstep.org true\nnct.org.uk true\nndarville.com true\nndbt.com true\nnder.be true\nne-on.org true\nne1home.dyndns.org true\nnearby.in.th true\nnebra.io true\nnecesitodinero.org true\nnectarleaf.com true\nnedcdata.org true\nnedraconsult.ru true\nneel.ch true\nneer.io true\nneftebitum-kngk.ru true\nneg9.org false\nnegai.moe true\nnegativecurvature.net true\nneilgreen.net true\nneilwynne.com false\nneko-life.com true\nneko.li true\nnekomimi.pl false\nnekosc.com true\nnelhage.com true\nnella-project.org true\nnellacms.com true\nnellacms.org true\nnellafw.org true\nneo19.com true\nnepustil.net true\nnerd42.de true\nnerdjokes.de true\nnerdtime.de true\nnerdydev.net true\nneris.io true\nnerull7.info true\nnet-safe.info true\nnetbank.com.au true\nnetbrief.ml true\nnetbulls.io true\nnetera.se true\nnetfs.pl false\nnetfxharmonics.com false\nnethackwiki.com true\nnetherwind.eu true\nnetlocal.ru true\nnetmagik.com true\nnetmazk.net true\nnetnodes.net true\nnetprofile.com.au true\nnetrelay.email true\nnetrider.net.au false\nnetronix.be true\nnetsigna.de true\nnetsoins.org true\nnetsparker.com false\nnetsystems.pro true\nnette.org true\nnettefoundation.com true\nnettools.link true\nnettopower.dk true\nnetulo.com true\nnetvizura.co.uk true\nnetwerkmanager.nl true\nnetwork-notes.com true\nnetwork23.nl true\nnetworkalarmcorp.com true\nnetworking4all.com true\nnetworkingnexus.net true\nnetworkingphoenix.com true\nnetworx-online.de true\nnetzvieh.de true\nnetzwerkwerk.de true\nnetzzwerg4u.de true\nneuhaus-city.de true\nneuwal.com true\nnevadafiber.net true\nnever-afk.de true\nnew-black-order.com true\nnew-process.ch true\nnewantiagingcreams.com true\nnewedivideo.it true\nnewkaliningrad.ru true\nnewline.online true\nnewlooknow.com true\nnewmediaone.net true\nnewodesign.com true\nnewpathintegratedtherapy.com true\nnewportpropertygroup.com true\nnewstarnootropics.com true\nnewstone-tech.com true\nnewsyslog.org true\nnewtnote.com true\nnewtonhaus.com true\nnex.sx true\nnexlab.org true\nnext176.sk true\nnext24.io true\nnextend.net true\nnextgencel.com true\nnfo.so true\nnfrost.me true\nng-firewall.com true\nnghe.net true\nniagaraschoice.org true\nnibiisclaim.com true\nnicestudio.co.il true\nnichteinschalten.de true\nnichthelfer.de true\nnickloose.de true\nnickrickard.co.uk true\nnicoborghuis.nl true\nnicocourts.com true\nnicoknibbe.nl true\nnicolaeiotcu.ro true\nnicolasbettag.me true\nnicolasklotz.de true\nnicolaw.uk true\nnidux.com true\nniduxcomercial.com true\nnierenpraxis-dr-merkel.de true\nnierenpraxis-merkel.de true\nnieselregen.com false\nniftiestsoftware.com true\nniggemeier.cc true\nnightfirec.at true\nnightfirecat.com true\nnightwinds.tk true\nnijm.nl true\nnikao-tech.com true\nnikklassen.ca true\nnikksno.io true\nniklaslindblad.se true\nnikobradshaw.com true\nnikolasbradshaw.com true\nnikolasgrottendieck.com true\nniloxy.com true\nnilrem.org true\nninchat.com true\nnine-hells.net false\nninespec.com true\nniouininon.eu true\nnippombashi.net true\nnippon-oku.com true\nnirada.info true\nnixien.fr true\nnixmag.net true\nnkinka.de true\nnl-ix.net true\nnl.search.yahoo.com false\nnlegall.fr true\nnmd.so true\nnmsnj.com true\nnnqc.nl false\nno.search.yahoo.com false\nnobly.de true\nnocit.dk true\nnocs.cn true\nnodari.com.ar true\nnodecompat.com true\nnodelia.com true\nnodespin.com true\nnodi.at true\nnoedidacticos.com true\nnoemax.com true\nnoez.de true\nnohats.ca true\nnohup.se true\nnoima.com true\nnoisetrap.cz true\nnojestorget.se true\nnolaviz.org true\nnolberg.net true\nnolte.work false\nnomadproject.io true\nnomesbiblicos.com true\nnomial.co.uk true\nnomorebytes.de true\nnoname-ev.de true\nnoob-box.net false\nnoobunbox.net true\nnoop.ch true\nnoordsee.de true\nnorandom.com true\nnord-sud.be true\nnordiccasinocommunity.com true\nnordseeblicke.de true\nnorrliden.de true\nnorskpensjon.no true\nnorthcutt.com true\nnorthernmuscle.ca true\nnos-medias.fr true\nnos-oignons.net true\nnossasenhoradodesterro.com.br true\nnostraforma.com false\nnotbolaget.se true\nnotenoughtime.de true\nnotificami.com true\nnotjustbitchy.com true\nnotoriousdev.com true\nnottheonion.net true\nnottres.com true\nnouvelle-vague-saint-cast.fr true\nnovacoast.com true\nnovafreixo.pt true\nnovawave.ca true\nnovelfeed.com true\nnowhere.dk true\nnowlas.org true\nnoxlogic.nl true\nnpmcdn.com true\nnpw.net true\nnsboston.org true\nnsboutique.com true\nnshost.ro true\nnspeaks.com true\nnsweb.solutions true\nntbs.pro true\nntppool.org true\nnube.ninja true\nnukenet.se true\nnukute.com true\nnull.cat true\nnumber.me true\nnumberoneshoes.co.nz true\nnumero-aleatorio.com true\nnurserybook.co true\nnusatrip-api.com true\nnutleyeducationalfoundation.org true\nnutleyef.org true\nnutrienti.eu true\nnutritionculture.com true\nnuttyveg.com true\nnuvini.com true\nnuxer.fr true\nnvlop.xyz true\nnwa.xyz true\nnwgh.org true\nnwra.com true\nnwwc.dk true\nnyan.it true\nnyffo.com false\nnyiad.edu true\nnyip.co.uk true\nnyip.edu true\nnymphetomania.net true\nnyronet.de false\nnysepho.pw true\nnyyu.tk true\nnzbs.io false\no0o.one true\no6asan.com true\noaic.gov.au true\noakslighting.co.uk true\noasis.mobi true\noasisim.net true\nobdolbacca.ru true\nobermeiers.eu true\nobscuredfiles.com false\nobsidianirc.net true\nobsproject.com true\nobsydian.org false\noccentus.net true\noceandns.eu true\noceandns.net true\noceandns.nl true\nochsundjunior.ch true\nockendenhemming.co.uk true\nocotg.com true\nocrami.us true\noctanio.com true\noctav.name true\noctocat.ninja true\noctothorpe.club true\noddtime.net true\nofcourselanguages.com true\noffshoot.rentals true\noffshore-firma.org true\noftn.org true\nogocare.com true\nogogoshop.com true\noguya.ch true\noh14.de true\nohiohealthfortune100.com true\nohling.org true\nohnemusik.com true\nohsocool.org true\noiepoie.nl false\nokane.love true\noke.com.tw true\nokmx.de true\nokok-rent.com true\nokok.rent true\nokonetwork.org.uk true\nokutama.in.th true\nolafnorge.de false\nolcso-vps-szerver.hu true\noleksii.name true\nolivierlemoal.fr true\nollie.io true\nollies.cloud true\nolliespage.net true\nollning.com true\nolmari.fi true\nols.io true\nomacostudio.com true\nomgaanmetidealen.com true\nomifind.com true\nomitech.co.uk true\nomniasl.com true\nonaboat.se true\nonarto.com true\nondrej.org true\none---line.com true\nonearth.one true\nonedot.nl true\nonedrive.com true\nonedrive.live.com false\nonefour.ga true\nonehourloan.sg true\noneminute.io true\noneminutefilm.tv true\nonespiritinc.com true\noneway.ga true\noneweb.hu true\nonewebdev.info true\noneworldbank.com true\nonguardonline.gov true\noniichan.us true\nonixcco.com.br true\nonline-bouwmaterialen.nl true\nonline-wetten.de true\nonline.swedbank.se true\nonlinebizdirect.com true\nonlinecensorship.org true\nonlinecompliance.org true\nonlinedemo.hu true\nonlinedeposit.us true\nonlinelegalmarketing.com true\nonlinelegalmedia.com true\nonlinelighting.com.au true\nonlinetravelmoney.co.uk true\nonly-roses.com true\nonmaps.de true\nonmarketbookbuilds.com true\nono.es true\nonqproductions.com true\nons.ca true\nonsitemassageco.com true\nonthebriteside.com true\nontimestamp.com true\nontras.com true\nonvori.com true\nonvori.de true\nonysix.net true\nonyxwall.link true\nonyxwall.net true\noogami.name true\noopsmycase.com true\nooyo.be true\nopen-bs.com true\nopen-bs.ru true\nopen-future.be true\nopen-mesh.org true\nopen.gl true\nopenacademies.com true\nopenblox.org true\nopenbsd.id true\nopenconnect.com.au true\nopenkvk.nl true\nopenmind-shop.de true\nopenmtbmap.org true\nopennippon.com true\nopennippon.ru true\nopensourcehouse.net true\nopenstreetmap.is true\nopentrack.info true\nopentrash.org true\nopenverse.com true\nopenvz.org true\nopenxmpp.com true\nopim.ca true\nopium.io true\noplop.appspot.com true\nopperwall.net true\nopq.pw true\noprbox.com true\noprechtgezegd.nl true\nopreismetingvoorunicef.nl true\nopsbears.com true\nopsmate.com false\nopstacks.com true\noptimista.soy true\noptimus.io true\noptimuscrime.net true\noptmos.at true\noptometriepunt.nl true\nopus-codium.fr true\noracaodocredo.com.br true\norangutan-appeal.org.uk true\norbiosales.com true\norbitcom.de true\norcahq.com true\norcamoney.com true\nordereat.fr true\norderswift.com true\norf-digitalsatkarte.at true\norf-kartentausch.at true\norganic-superfood.net true\noricejoc.com true\norientalart.nl true\norioncustompcs.com false\norlives.de true\noroweatorganic.com true\nosaiyuwu.com true\noscarvk.ch true\noscsdp.cz true\noshayr.com true\nosm.is true\nosmanlitorunu.com true\nosmosis.org true\nosp.cx true\nosquery.io true\nostendorf.com true\nosterkraenzchen.de true\nosticketawesome.com true\nostr.io true\noszri.hu true\notakurepublic.com true\notichi.com true\notoy.com true\notpsmart.com.ua true\notrsdemo.hu true\nottoproject.io true\notya.me true\nourcloud.at true\nourevents.net true\noutdoorproducts.com true\noutetc.com true\noveralglas.nl true\noverclockers.ge true\noverkillshop.com true\noverride.io true\noverseamusic.de true\noversight.garden true\noversight.io true\novpn.to true\novvy.net true\nowensmith.website true\nown3d.ch true\nowncloud.help true\nownit.se false\noxygaming.com true\noxygenabsorbers.com true\noxymc.com true\noxynux.xyz true\noyste.in true\noznamovacipovinnost.cz true\nozvolvo.org true\np-s-b.com true\np1c.pw true\np3in.com true\npace.car true\npacelink.de true\npackagist.org false\npacker.io true\npacoda.de true\npactf.com true\npaestbin.com true\npagerate.io true\npagetoimage.com true\npagewizz.com false\npaginapolitica.ro true\npagure.io true\npagure.org true\npainosso.org true\npajonzeck.de true\npajowu.de true\npajuvuo.fi true\npaket.io true\npakke.de true\npakremit.com true\npaku.me true\npalatin.at true\npalationtrade.com true\npalava.tv true\npamplona.tv true\npamsoft.pl true\npan.digital true\npanaceallc.net true\npanamateakforestry.com true\npaneu.de true\npanmetro.com true\npanoti.com true\npansu.space true\npanthur.com.au false\npantou.org false\npantsu.cat true\npap.la false\npapa-webzeit.de true\npapayapythons.com true\npaperturn.com true\npaperwork.co.za true\nparadoxdesigns.org true\nparagon.com.sg true\nparagonie.com true\nparagreen.net true\nparanoxer.hu true\nparasitologyclub.org true\nparatlan.hu true\nparent5446.us true\nparentinterview.com true\nparentmail.co.uk true\npariga.co.uk true\nparithy.net true\nparkingplus.co.il true\nparkingpoint.co.uk true\nparlamento.gub.uy true\nparleu2016.nl true\nparleur.net true\nparsemail.org true\nparticonpsplus.it true\npartirkyoto.jp true\npartnerbeam.com true\npartnercardservices.com true\npartyvan.eu true\npassieposse.nl true\npassphrase.today true\npassport.yandex.by true\npassport.yandex.com true\npassport.yandex.com.tr true\npassport.yandex.kz true\npassport.yandex.ru true\npassport.yandex.ua true\npasswd.io true\npassword.codes true\npasswordrevelator.net true\npasswords.google.com true\npasta-factory.co.il true\npastaenprosecco.nl false\npastaf.com true\npastenib.com true\npasteros.io true\npataua.kiwi true\npatechmasters.com true\npatentfamily.de true\npaternitydnatest.com true\npatfs.com true\npatralos.at false\npatrickschneider.me true\npatriksimek.cz true\npaul.reviews true\npauladamsmith.com true\npaulbdelaat.nl true\npaulinewesterman.nl true\npaulproell.at true\npaulschreiber.com true\npaulyang.cn true\npauspam.net true\npay.ubuntu.com true\npayfreez.com true\npaylike.io true\npayment-network.com true\npaymentaccuracy.gov true\npayments-reference.org true\npaymill.com true\npaymill.de true\npayoff.com true\npaypal.com false\npaypaq.com true\npaypro.nl false\npayroll.ch true\npayroll.xero.com false\npaysera.com true\npayslipview.com true\npaytm.in true\npaytwopay.com true\npayupay.ru true\npbapp.net true\npbscreens.com true\npcel.com true\npcfeuerwehr.de true\npcforum.sk true\npclob.gov true\npcloud.com true\npctonic.net true\npdamsidoarjo.co.id true\npdevio.com true\npdf.yt true\npe.search.yahoo.com false\npeaceandwool.com true\npeakapp.nl true\npear2pear.de true\npedicureduiven.nl true\npedroventura.com true\npeercraft.com true\npeerherrmann.de true\npeername.com true\npeervpn.net true\npeifi.de true\npekkapikkarainen.fi true\npekkarik.ru true\npekoe.se true\npelanucto.cz true\npencepay.com true\npenfold.fr true\npengi.me true\npenguinclientsystem.com true\npennergold.net true\npennyapp.io true\npennylane.me.uk true\npensiunealido.ro true\npentano.net true\npentest.nl true\npentesterlab.com true\npeoplesbankal.com true\npepchid.com false\npepperhead.com true\npepperworldhotshop.de true\nper-pedes.at true\npercolate.com true\nperdel.cn true\nperfect.in.th true\nperfectseourl.com true\nperfektesgewicht.com true\nperfektesgewicht.de true\nperformancesantafe.org true\nperformaterm.ro true\nperishablepress.com true\nperot.me true\nperplex.nl false\nperspectivum.com true\npersson.im true\nperthdevicelab.com true\npestici.de true\npet-nsk.ru true\npetabits.de true\npetchart.net true\npeter.org.ua true\npeterboers.info true\npeterdavehello.org true\npeterfolta.net true\npetersmark.com false\npethub.com true\npetja.me false\npetko.me true\npetplum.com true\npetplus.com true\npetpost.co.nz true\npetrachuk.ru true\npetravdbos.nl true\npetruzz.net true\npetsittersservices.com true\npewboards.com true\npeytonfarrar.com true\npfadfinder-aurich.de true\npfarchimedes-pensioen123.nl true\npfd-nz.com true\npfolta.net true\npgmann.cf true\npgmsource.com true\npgpm.io true\npgpmail.cc true\npgregg.com true\npgtb.be true\nph.search.yahoo.com false\nphantasie.cc true\npharmaboard.de true\npharmgkb.org true\npharynks.com true\nphil.tw true\nphiladelphia.com.mx true\nphiladelphiadancefoundation.org true\nphilipkohn.com true\nphilipmordue.co.uk true\nphillmoore.com true\nphillprice.com true\nphilosopherswool.com true\nphilosophyguides.org true\nphilphonic.de true\nphoebe.co.nz true\nphoenix.dj true\nphoenixlogan.com true\nphormance.com true\nphoto.org.il true\nphp-tuning.de true\nphparcade.com true\nphpdistribution.com true\nphpdorset.co.uk true\nphperformances.fr true\nphpfashion.com true\nphpsecure.info true\nphryanjr.com false\nphryneas.de true\nphunehehe.net true\npi-supply.com true\npicardiascr.com true\npickme.nl true\npicoauto.com true\npiconepress.com true\npicotech.com true\npicsto.re true\npiekacz.co.uk true\npiekacz.eu.org true\npiekacz.net true\npieperhome.de true\npieq.eu true\npieq.eu.org true\npier28.com true\npierre-denoblens.net true\npierre-schmitz.com true\npieterhordijk.com true\npieterjangeeroms.me true\npijuice.com true\npileofgarbage.net true\npilgermaske.org true\npiliszek.net true\npimpmymac.ru true\npims.global true\npincha.com.tw true\npindanutjes.be true\npinkcasino.co.uk true\npinkhq.com true\npinnaclelife.co.nz true\npinnaclelife.nz true\npipenny.net true\npir9.com true\npirateproxy.la true\npirateproxy.pe true\npirateproxy.pl true\npirateproxy.pw true\npirateproxy.sx true\npirateproxy.tv true\npirlitu.com true\npirxpilot.me true\npisexy.me true\npisupp.ly true\npitfire.io false\npittonpreschool.com true\npiwko.co true\npixel.facebook.com false\npixel.google.com true\npixelbash.de true\npixelhero.co.uk true\npixelminers.net true\npixi.chat true\npiyabute.com true\npjuu.com false\npkgt.de true\npl.search.yahoo.com false\nplacefade.com true\nplacehold.co true\nplacollection.org true\nplaettliaktion.ch true\nplainjs.com false\nplaintech.net.au true\nplaintray.com true\nplanboardapp.com true\nplanet-work.com true\nplanete-cocoon.com true\nplanpharmacy.com true\nplasti-pac.ch true\nplay.google.com true\nplaykh.com true\nplaymaker.io true\nplease-deny.me true\npleier-it.de true\npleier.it true\nplexusmd.com true\nplfgr.eu.org true\nplhdb.org true\npliosoft.com true\nplirt.ru false\nplixer.com true\nploader.ru false\nplogable.co true\nploxel.com true\npluff.nl true\npluga.co true\nplugcubed.net false\nplugin-planet.com true\nplumlocosoft.com true\nplus.google.com false\nplus.sandbox.google.com false\nplzenskybarcamp.cz true\npm13.cz true\npm13.org true\npmctire.com true\npmg-offshore-company.com true\npmg-purchase.com true\npmg-purchase.net true\npmponline.de true\npmt-documenten.nl true\npo.gl true\npocketsix.com true\npocloud.homelinux.net true\npoed.com.au true\npoedgirl.com true\npointaction.com true\npointagri.com true\npointiswunderland.de true\npointpro.de true\npokeinthe.io true\npokemori.jp true\npoleartschool.com true\npoliceiwitness.sg true\npolis.or.at true\npolis.to false\npolitic.org.ua true\npolitiewervingshop.nl true\npollpodium.nl true\npolymathematician.com true\npolynomapp.com true\npolypet.com.sg true\npompompoes.com true\nponolau.com true\npontualcomp.com true\nponythread.com false\npoolvilla-margarita.net false\npoon.io false\npoon.tech true\nporis.web.id true\npornstars.me true\nportal.tirol.gv.at true\nportalzine.de true\nportercup.com true\nportosonline.pl true\nportraitsystem.biz true\nportvaletickets.com true\nposobota.cz true\npost4me.at true\npostal.dk true\npostbox.life true\npostcodegarant.nl true\npostcodewise.co.uk true\nposteo.de false\npostfinance.ch true\npostn.eu true\npostpi.com true\nposttigo.com true\npotatofrom.space true\npotatoheads.net true\npotbar.com true\npotbox.com true\npothe.com true\npothe.de true\npotlytics.com true\npotpourrifestival.de true\npovitria.net true\npower99press.com true\npowercloud.technology true\npowerentertainment.tv true\npowergridess.com true\npowerwellness-korecki.de true\nppipe.net true\nppmoon.com true\nppro.com true\nppy3.com true\npr1sm.com true\npr2studio.com true\npracticallabs.com true\nprakharprasad.com true\nprayerrequest.com true\nprazynka.pl true\nprecedecaritas.com.br true\npredoiu.ro true\nprefis.com true\nprego-shop.de true\npreisser-it.de true\nprekladysanca.cz true\npreloaded-hsts.badssl.com true\npremierheart.com true\npremiumzweirad.de false\nprepandgo-euro.com true\npreparetheword.com false\nprescotonline.co.uk true\nprescriptiondrugs.com true\npresidentials2016.com true\npress-anime-nenkan.com true\npressrush.com true\nprestburyscouts.org.uk true\npretix.eu true\npretty.hu true\nprettytunesapp.com true\npreworkout.me true\nprezola.com true\nprgslab.net false\npridetechdesign.com true\npridoc.se true\nprilock.com true\nprincessmargaretlotto.com true\nprincipaltoolbox.com true\nprior-it.be true\npriva.si true\nprivacy.com true\nprivacyinternational.org true\nprivacylabs.io true\nprivategiant.com true\nprivatepokertour.com true\nprivaterelay.com true\nprivatestatic.com true\nprivytime.com true\nprjktruby.com true\npro-bike.ro true\npro-link.eu true\npro-zone.com true\nprocensus.com true\nprofessionalboundaries.com true\nprofidea.cz true\nprofiles.google.com true\nprofivps.com true\nprofpay.com true\nprofundr.com true\nprogblog.net true\nprogg.no true\nprogreso.pl true\nprogressiveplanning.com true\nprohostonline.fi true\nproitconsulting.com.au true\nprojectarmy.net true\nprojectascension.io true\nprojectbenson.com true\nprojectblackbook.us true\nprojektzentrisch.de true\npromoscuola.net true\npromotiongeeks.com false\nprontocleaners.co.uk true\nprontomovers.co.uk true\nproofwiki.org true\nproos.nl true\npropactrading.com true\npropagandism.org true\npropershave.com true\nproperty-catalogue.eu true\npropipesystem.com true\nproslimdiets.com true\nprosocialmachines.com true\nprosoft.sk true\nprospo.co true\nproteus-tech.com true\nprotonmail.ch true\nprotonmail.com true\nprotoyou.de true\nproust.media true\nproustmedia.de true\nproxybay.co true\nproxybay.la true\nproxybay.top true\nproxybay.tv true\nproxyweb.us true\nprtpe.com true\nprvikvadrat.hr true\nprytkov.com true\nprzemas.pl true\npsb1911.com true\npsicologia.co.ve true\npsncardplus.be true\npsncardplus.com true\npsncardplus.dk true\npsncardplus.nl true\npsncardplus.se true\npste.pw true\npsw-group.de true\npsw.academy true\npsw.consulting true\npsw.net true\npsxtr.com true\npt-server.de true\nptbx.co true\npterodactylus.cz true\nptgoldensun.com true\nptm.ro true\nptsoft.de true\npuac.de true\npublications.qld.gov.au false\npublicsuffix.org true\npubreviews.com true\npucssa.org true\npuddis.de true\npugliese.fr true\npuikheid.nl true\npuiterwijk.org true\npulsar.guru true\npumpgames.net true\npunchkickinteractive.com true\npunkapoule.fr true\npunknews.org true\npupboss.com true\npurplebricks.com true\npurplemoon.ch true\npurplestar.ch true\npurplestar.com true\npuryearlaw.com true\npuyblanc.info true\npvcvoordeel.nl false\npvtschlag.com true\npwd.ovh true\npwnies.dk true\npwntr.com true\npxx.io true\npygarage.com true\npyol.org true\npypa.io true\npypi.io true\npypi.python.org true\npysays.net true\npython.org false\nqa.fedoraproject.org true\nqa.stg.fedoraproject.org true\nqapital.com true\nqbeing.info true\nqc.search.yahoo.com false\nqccareerschool.com true\nqcdesignschool.com true\nqceventplanning.com true\nqcmakeupacademy.com true\nqcstudentcenter.com true\nqcstyleacademy.com true\nqctravelschool.com true\nqetesh.de true\nqewc.com true\nqgustavor.tk true\nqikan.net true\nqiliang.wang true\nqingpat.com true\nqingpei.me true\nqionglu.pw true\nqixxit.de true\nqkka.org true\nqldformulaford.org true\nqlrace.com false\nqonqa.de true\nqop.io true\nqorm.co.uk true\nqrlending.com true\nqtl.me true\nqtpower.co.uk true\nqtxh.net true\nquai10.org true\nquail.solutions true\nquakelive.dk true\nqualityedgarsolutions.com true\nqualityhomesystems.com true\nqualityofcourse.com true\nqualityology.com true\nquantacloud.ch true\nquantenteranik.eu true\nquantoras.com true\nquantumfurball.net true\nquchao.com true\nquebecmailbox.com true\nqueercinema.ch true\nqueercoders.com true\nquera.ir true\nquestsandrewards.com true\nquickpayservice.com true\nquietapple.org true\nquikpay.com.au true\nquintessa.org true\nquire.io true\nquli.nl true\nquotehex.com true\nquotemaster.co.za true\nquotev.com true\nquppa.net true\nquranserver.net true\nquuz.org true\nqvitoo.com true\nqwant.com true\nqwilink.me true\nr0uzic.net true\nr10n.com true\nr3s1stanc3.me true\nr40.us true\nr6-team.ru true\nr811.de true\nraah.co true\nraajheshkannaa.com true\nracasdecachorro.org true\nracermaster.xyz false\nraceviewcycles.com true\nraceviewequestrian.com true\nracius.com true\nraconconsulting.co.uk true\nrad-route.de true\nradar.sx true\nradicaleducation.net true\nradtke.bayern true\nradyn.com true\nrafaelcz.de true\nrage4.com true\nragingserenity.com true\nraidstone.com true\nraidstone.net true\nraidstone.rocks true\nrailgun.ac true\nrailyardurgentcare.com true\nrainforest.engineering true\nraiseyourflag.com true\nraitza.de true\nrak-business-service.com true\nram-it.nl true\nramatola.uk true\nramon-c.nl true\nramonj.nl true\nramsor-gaming.de true\nrandc.org true\nrandomcage.com true\nrandstaddirect.nl false\nrangde.org true\nrantanda.com true\nrapenroer.nl true\nrapido.nu true\nraspass.me true\nratajczak.fr true\nratd.net true\nratuseks.net true\nratuseks.us true\nrauros.net true\nrautermods.net true\nravchat.com true\nravindran.me true\nrawoil.com true\nrawsec.net true\nray-home.de true\nray-works.de true\nraydan.space true\nraymd.de true\nraymii.org true\nraymondjcox.com true\nrayworks.de true\nrazlaw.name true\nrbensch.com true\nrbhighinc.org true\nrburchell.com true\nrbxcatalog.com true\nrcafox.com true\nrcnitrotalk.com true\nrcorporation.be true\nrdh.asia true\nreachr.com true\nreaconverter.com true\nreactivarte.es true\nreader.ga true\nreadr.pw true\nreal-it.nl true\nrealcapoeira.ru true\nrealgarant-shop.de false\nreallifeforums.com true\nrealmic.net true\nrealmofespionage.xyz true\nrealwaycome.com true\nreanimated.eu true\nreardenporn.com true\nrebootmc.com true\nrecapp.ch true\nrecepty.eu true\nrecht-freundlich.de true\nrecon-networks.com true\nrecordeuropa.com true\nrecyclingpromotions.us true\nred-t-shirt.ru true\nredar.xyz true\nredb.cz true\nredballoonsecurity.com true\nredbee.nl true\nredburn.com true\nredd.it true\nreddingsbrigade-zwolle.nl true\nreddit.com true\nreddit2kindle.com true\nrede-reim.de true\nrede.ca true\nredigest.it true\nredirect.fedoraproject.org true\nredirect.stg.fedoraproject.org true\nredit.com true\nredletter.link true\nredlink.de true\nredmbk.com true\nrednsx.org true\nredports.org false\nredra.ws true\nredshield.co true\nredshiftcybersecurity.co.za true\nredstickfestival.org true\nredteam-pentesting.de true\nredzurl.com true\nreedloden.com true\nreezer.org true\nrefill-roboter.de true\nrefreshingserum.com true\nrefundo.cz true\nrefundo.sk true\nreg.ru false\nregalosymuestrasgratis.com true\nregar42.fr false\nregendevices.eu true\nreggae-cdmx.com true\nregionalcoalition.org true\nregionale.org true\nregiovertrieb.de true\nregister.gov.uk true\nregmyr.se true\nreiki-coaching.nl true\nreimers.de true\nreishunger.de true\nreisyukaku.org true\nreithguard-it.de true\nrelaxhavefun.com true\nrelayawards.com true\nrelease-monitoring.org true\nreliable-mail.de true\nrem.pe true\nremambo.jp true\nrememberthemilk.com false\nremitatm.com true\nremonti.info true\nremotestance.com true\nremoteutilities.com true\nrenderloop.com true\nrene-schwarz.com true\nrenemoser.net true\nrenkenlaw.com true\nrenlong.org true\nrenrenche.com false\nrentacarcluj.xyz true\nrentinsingapore.com.sg true\nrenuo.ch true\nreox.at true\nrepaxan.com true\nreplicagunsswords.com false\nreport-uri.com true\nreport-uri.io true\nreporturi.com true\nreporturi.io true\nrepublique.org true\nrepustate.com true\nreputationweaver.com true\nres-rheingau.de true\nres42.com true\nresc.la true\nresearch.facebook.com false\nresearch.md true\nresidentsinsurance.co.uk true\nresist.ca true\nresources.flowfinity.com true\nresponsibledisclosure.nl true\nrestchart.com true\nrestrito.org true\nretcor.net true\nretroarms.com true\nretroarms.cz true\nretrofitlab.com true\nretrotracks.net true\nreucon.com true\nrevamed.com false\nrevapost.ch true\nrevello.org true\nrevensoftware.com true\nrevlect.com true\nrevolt.tv true\nrevthefox.co.uk true\nrewrite3.com true\nrex.st true\nrex.tc true\nrezept-planer.de true\nrezosup.net true\nrezosup.org true\nrgavmf.ru true\nrhapsodhy.hu true\nrheocube.com true\nrhering.de true\nrheuma-online.de true\nrhinelander.ca true\nrhodenmanorcattery.co.uk true\nrhymix.org true\nrhynl.io true\nribs.com true\nriccy.org true\nrichardhering.de true\nrichardwarrender.com true\nrichmondsunlight.com false\nrichsiciliano.com true\nricki-z.com true\nrickyromero.com true\nricochet.im true\nrid-wan.com false\nride-up.com true\nriesenmagnete.de true\nriesenweber.id.au true\nrigart-michael.be true\nrigartmichael.be true\nright-to-love.name true\nrightcapital.com true\nrighttobuy.gov.uk true\nrijschoolgevonden.nl false\nring0.xyz false\nringh.am true\nringingliberty.com true\nriscascape.net true\nrischard.org true\nriseup.net true\nriskmgt.com.au true\nriskmitigation.ch true\nristioja.ee true\nrithm.ch true\nriversideauto.net true\nrivy.org true\nrix.ninja true\nriyono.com true\nrk6.cz true\nrlalique.com true\nrmb.li true\nrmmanfredi.com true\nrms.sexy true\nrmstudio.tw true\nrngmeme.com true\nro.search.yahoo.com false\nroave.com true\nrobandjanine.com true\nroberthurlbut.com true\nrobertkrueger.de true\nrobertof.ovh true\nrobhorstmanshof.nl true\nrobi-net.it true\nrobigalia.org true\nrobinadr.com true\nrobinsonyu.com true\nrobinwinslow.uk true\nrobodeidentidad.gov true\nrobohash.org true\nrobspc.repair true\nrobteix.com true\nrobtex.com true\nrobtex.net true\nrobtex.org true\nrobud.info true\nrockcanyonbank.com true\nrocketmill.co.uk true\nrodehutskors.net true\nrodney.id.au true\nrodolfo.gs true\nroeckx.be true\nroeitijd.nl true\nroelf.org true\nroffe.nu true\nrogue-e.xyz true\nrohedaten.de true\nrohlik.cz true\nrointe.online true\nrokki.ch true\nrokort.dk true\nroland.io true\nrolandreed.cn true\nrolemaster.net true\nromab.com true\nromaimperator.com true\nromainmuller.xyz true\nroman-pavlik.cz true\nromeoferraris.com false\nromulusapp.com false\nron2k.za.net true\nroom-checkin24.de true\nroom208.org true\nroombase.nl true\nroomhub.jp true\nroosterpgplus.nl true\nroot.eu.org true\nrootforum.org true\nrootrelativity.com true\nroots.io true\nrootservice.org true\nrootswitch.com true\nroquecenter.org true\nrosenkeller.org true\nrossen.be true\nrot47.net true\nrotozen.com true\nrotterdamjazz.info true\nrottweil-hilft.de true\nrotunneling.net true\nrotzonline.com false\nroundcube.mayfirst.org false\nrous.se true\nrouvray.org true\nrowancasting.ie true\nroyalacademy.org.uk true\nroyalhop.co true\nroyalmarinesassociation.org.uk true\nroyalpalacenogent.fr true\nroyalpub.net true\nroyalvisiongroup.com true\nroyzez.com true\nrozalisbengal.ro true\nrpy.xyz true\nrr105.de true\nrring.me true\nrrke.cc true\nrsajeey.info true\nrsampaio.info true\nrsauget.fr true\nrsi.im false\nrssr.se true\nrsync.eu true\nrtcx.net true\nrtd.uk.com true\nru-sprachstudio.ch true\nru.search.yahoo.com false\nruanmi.de true\nrubbermaidoutlet.com true\nrubendv.be true\nrubi-ka.net true\nrubysecurity.org true\nrudeotter.com true\nruderverein-gelsenkirchen.de true\nruffbeatz.com true\nrugk.dedyn.io true\nrugstorene.co.uk true\nruh-veit.de true\nruhrmobil-e.de true\nrunawebinar.nl true\nruncarina.com true\nrunreport.fr true\nrunway2street.com true\nruobiyi.com true\nrusadmin.biz true\nrusempire.ru true\nrusl.net true\nrutgerschimmel.nl true\nruudkoot.nl true\nrwanderlust.com true\nrws-vertriebsportal.de true\nrww.name true\nrxbusiness.com true\nryan-goldstein.com true\nryankearney.com true\nryanmcdonough.co.uk true\nryansmithphotography.com false\ns-c.se true\ns-cubed.net true\ns-mdb.com true\ns13d.fr true\nsaba-piserver.info true\nsabahattin-gucukoglu.com true\nsaccani.net true\nsafar.sk true\nsafcstore.com true\nsafejourney.education true\nsafematix.com true\nsafeme.ga true\nsafemovescheme.co.uk true\nsafescan.com true\nsafic.net true\nsagedocumentmanager.com true\nsagerus.com true\nsageth.com true\nsagsmarseille.com true\nsaharalondon.com true\nsaikarra.com false\nsailormoonevents.org false\nsaintsrobotics.com true\nsalaervergleich.com true\nsale4ru.ru true\nsaleaks.org true\nsaleslift.pl true\nsalmo23.com.br true\nsalmododia.net true\nsalmos91.com true\nsalon.io false\nsaltercane.com false\nsaltstack.cz true\nsalverainha.org true\nsamaritansnet.org true\nsamba.org true\nsambeso.net true\nsamegoal.com true\nsamegoal.org true\nsamfunnet.no false\nsamifar.in true\nsamirnassar.com true\nsamizdat.cz true\nsamkelleher.com true\nsaml-gateway.org true\nsaml2.com true\nsamraskauskas.com true\nsamuelkeeley.com true\nsamwu.tw false\nsanandreasstories.com true\nsanasalud.org true\nsanasport.cz true\nsanatfilan.com true\nsanchez.adv.br true\nsandbagexpress.com true\nsandbox.mydigipass.com false\nsanderdorigo.nl true\nsandervankasteel.nl true\nsandobygg.se true\nsandogruppen.se true\nsandor.wtf true\nsanglierhurlant.fr true\nsangwon.io true\nsanhei.ch true\nsanissimo.com.mx true\nsanradon.by true\nsantanderideas.com true\nsaorsat.ie true\nsapience.com true\nsarah-beckett-harpist.com true\nsarahbeckettharpist.com true\nsarahlicity.co.uk false\nsarahlicity.me.uk true\nsarahs-roestcafe.de false\nsarahsweetlife.com true\nsarakas.com true\nsaraleebread.com true\nsarasturdivant.com true\nsardegnatirocini.it true\nsarindia.de true\nsarisonproductions.com true\nsaro.me true\nsaruwebshop.co.za true\nsash.pw true\nsaskpension.com true\nsasyabapi.com true\nsat.rent true\nsat4all.com true\nsatmep.com true\nsatrent.com true\nsatrent.se true\nsatriyowibowo.my.id true\nsatsukii.moe true\nsaturne.tk true\nsaucyfox.net true\nsauerbrey.eu true\nsaulchristie.com true\nsaunas.fr true\nsave.gov true\nsaveaward.gov true\nsavekorea.net true\nsavenet.org true\nsavetheinternet.eu true\nsavic.com true\nsavingsstoreonline.ca true\nsavvytime.com true\nsazuz.cz true\nsbirecruitment.co.in true\nsbox-archives.com true\nsbssoft.ru true\nsby.de true\nscaling.solutions true\nscanpay.dk true\nschachburg.de true\nschallert.com false\nscheidtweiler.de true\nschlabbi.com true\nschlarp.com true\nschneids.me true\nschnellno.de true\nschnouki.net true\nschoepski.de true\nschokokeks.org true\nschool.in.th true\nschooltrends.co.uk true\nschoolze.com true\nschorelweb.nl true\nschreibnacht.de true\nschreinerei-jahreis.de true\nschritt4fit.de true\nschrodinger.io true\nschroepfglas-versand.de true\nschumanandmonnet.eu true\nschurkenstaat.net true\nschwarzer.it true\nschwarzkopfforyou.de true\nschwarzwaldcon.de true\nschwinabart.com true\nscicasts.com true\nscience-texts.de true\nsciencex.com true\nscottdial.com true\nscottgruber.me true\nscotthel.me true\nscotthelme.co.uk true\nscotthelme.com true\nscottstorey.co.uk true\nscourt.info true\nscourt.org.ua true\nscoutdb.ch true\nscp-trens.notaires.fr true\nscrap.tf true\nscrapings.net true\nscrayos.net true\nscreencaster.io true\nscreenlight.tv true\nscreenresolution.space true\nscrion.com true\nscript.google.com true\nscriptict.nl true\nscrollstory.com true\nscs-simulatoren.de true\nsculpture.support true\nsdcardrecovery.de true\nsdrobs.com true\nse.search.yahoo.com false\nseamless.no true\nseanholcroft.co.uk true\nsearch-one.de true\nsearch.yahoo.com false\nsearchbrothers.com true\nseasons.nu true\nseatbeltpledge.com true\nseattlefabrication.com true\nsebastianboegl.de true\nsebster.com true\nsec.gd true\nsecboom.com true\nsecctexasgiving.org false\nsecondary-survivor.com true\nsecondary-survivor.help true\nsecondary-survivor.net true\nsecondarysurvivor.help true\nsecondarysurvivorportal.com true\nsecondarysurvivorportal.help true\nsecpatrol.de true\nsecretpanties.com true\nsecretserveronline.com true\nsectia22.ro true\nsectio-aurea.org true\nsection.io true\nsectun.com true\nsecure-server-hosting.com true\nsecure.advancepayroll.com.au true\nsecure.chat true\nsecure.facebook.com false\nsecuredevelop.net true\nsecuredrop.org false\nsecureideas.com false\nsecureonline.co true\nsecurify.nl true\nsecurity-carpet.com true\nsecurity-taskforce.be true\nsecurity.google.com true\nsecurityheaders.com true\nsecurityheaders.io true\nsecuritymap.wiki true\nsecuritysnobs.com false\nsecuritysoapbox.com true\nsecuritystreak.com true\nsecuvera.de true\nsedoexpert.nl true\nsedoexperts.nl true\nsedziapilkarski.pl true\nseedbox.fr true\nseele.ca true\nseen.life true\nsegitz.de true\nsegulink.com true\nsegurosocial.gov false\nseida.at true\nseifried.org true\nseiko-dojo.com true\nselcusters.nl true\nselectel.ru true\nselectorders.com true\nselectruckscalltrackingreports.com true\nselent.me true\nself-injury.net true\nselfcarecentral.com true\nselfici.com true\nselfici.cz true\nselfserverx.com true\nselldorado.com true\nsellme.biz true\nsellocdn.com true\nseminariruum.ee true\nsemps-2fa.de true\nsemps.de true\nsemyonov.su true\nsemyonov.us true\nsendash.com true\nsendc.at true\nsendcat.com true\nsendmeback.de true\nsendya.me true\nsenedirect.com true\nseo-nerd.de true\nseo.consulting true\nsephr.com true\nseptakkordeon.de true\nsequencing.com true\nserbanpaun.ro true\nserenitycreams.com true\nserfdom.io true\nserized.pw true\nserveradminz.com true\nserverco.com true\nservercode.ca true\nservergno.me false\nserveroffline.net false\nserverpedia.de true\nserverstuff.info true\nservertastic.com true\nservethecity-karlsruhe.de false\nservious.org true\nseryo.net true\nseryovpn.com true\nsesha.co.za true\nsessionslogning.dk true\nsetfix.de true\nsetphaserstostun.org true\nsetuid.de true\nsetuid.io true\nsetuid0.kr true\nsevenmatches.com true\nsevsopr.ru true\nsexton.uk.com false\nsexwork.net true\nseyahatsagliksigortalari.com true\nseyr.me true\nsftool.gov true\nsg.search.yahoo.com false\nsgcaccounts.co.uk true\nsh-network.de true\nshaaaaaaaaaaaaa.com true\nshadex.net true\nshadowguardian507-irl.tk true\nshadowkitsune.net true\nshadowmorph.info true\nshadowsocks.net true\nshagi29.ru true\nshaitan.eu true\nshakepeers.org true\nshakes4u.com true\nshalott.org true\nshamariki.ru true\nshan.io true\nshanewadleigh.com true\nshannoneichorn.com true\nshaobin.wang true\nsharepass.pw true\nsharepic.xyz true\nsharepointdrive.com true\nsharescope.co.uk true\nsharesplitter.com true\nshareworx.net true\nsharvey.ca true\nshasso.com true\nshauncrowley.co.uk true\nshaundanielz.com true\nshawcentral.ca true\nshazbots.org true\nsheilasdrivingschool.com true\nshellfire.de true\nshellvatore.us true\nshenyuqi.com true\nshep.co.il true\nsherbers.de true\nshft.cl true\nshh.sh true\nshiftplanning.com true\nshinnyosangha.org false\nshiona.xyz true\nshipard.com true\nshipcloud.io true\nshiroki-k.net true\nshodan.io true\nshome.de true\nshopapi.cz true\nshopbakersnook.com true\nshoplandia.co true\nshorebreaksecurity.com true\nshortdiary.me true\nshortpath.com true\nshowsonar.com true\nshtorku.com true\nshu-kin.net true\nshulan.moe true\nshyrydan.es true\nsi-benelux.nl true\nsichere-kartenakzeptanz.de true\nsiciliadigitale.pro true\nsideshowbarker.net true\nsidium.de true\nsidnicio.us true\nsiebens.net true\nsieh.es true\nsiewert-kau.de true\nsifls.com true\nsig6.org true\nsigabrt.org true\nsightcure.jp true\nsighup.nz true\nsignere.com true\nsigning-milter.org true\nsignslabelstapesandmore.com false\nsigntul.com false\nsigterm.sh true\nsikatehtaat.fi true\nsikayetvar.com true\nsilentcircle.com false\nsilentkernel.fr true\nsilentlink.io true\nsilentundo.org true\nsilicagelpackets.ca true\nsilkebaekken.no true\nsillisalaatti.fi true\nsilver-heart.co.uk true\nsilverbowflyshop.com true\nsilverdragonart.com true\nsilvergoldbull.com true\nsilvergoldbull.de true\nsilverwind.io true\nsimbihaiti.com true\nsimbolo.co.uk false\nsimobilklub.si true\nsimod.org false\nsimon-hofmann.org true\nsimoncommunity.org.uk true\nsimongong.net true\nsimonkjellberg.com true\nsimonkjellberg.se true\nsimonreich.de true\nsimonsreich.de true\nsimpan.id true\nsimphony.cz true\nsimple.com false\nsimpleai.net true\nsimplednscrypt.org true\nsimplepractice.com true\nsimpletax.ca false\nsimplexsupport.com false\nsimplia.cz true\nsimplixos.org true\nsimplycharlottemason.com true\nsimplyfixit.co.uk true\nsimplymozzo.se true\nsimplystudio.com true\nsin30.net true\nsincron.org true\nsingleuse.link true\nsinglu10.org true\nsingul4rity.com true\nsinneserweiterung.de true\nsinoscandinavia.se true\nsinosky.org true\nsiraweb.org true\nsirenslove.com true\nsirius-lee.net true\nsiriuspup.com true\nsistem-maklumat.com.my true\nsistemy48.ru false\nsisv.eu true\nsitehost.io true\nsites.google.com true\nsitesko.de true\nsitesten.com true\nsizingservers.be false\nsizzle.co.uk true\nsjoorm.com true\nskalender.ch false\nskanvordoff.ru true\nskaraborgsassistans.com false\nskarrok.com true\nskatclub-beratzhausen.de true\nskatn.de true\nskeeley.com true\nskhoop.cz true\nski-insurance.com.au true\nskia.org true\nskigebiete-test.de true\nskilldetector.com true\nskilletfood.com true\nskills2services.com true\nskillseo.com true\nskimming.net true\nskipfault.com true\nskoda-clever-lead.de true\nskoda-im-dialog.de true\nskoda-nurdiebesten.de true\nskoda-service-team-cup.de true\nskogsbruket.fi true\nskogskultur.fi true\nskoleniphp.cz true\nskotty.io true\nskou.dk true\nskyasker.com true\nskydrive.live.com false\nskyflix.me true\nskyminds.net true\nskyveo.ml true\nskyway.capital true\nsl1pkn07.wtf true\nslack-files.com true\nslack.com true\nsladic.si false\nslainvet.net true\nslamdjapan.com true\nslamix.nl true\nslaps.be true\nslashdesign.it false\nslauber.de true\nsleio.com true\nslever.cz true\nslevomat.cz true\nsliceone.com true\nslicklines.co.uk true\nslidebatch.com true\nslightfuture.com true\nslix.io true\nsloancom.com true\nslotboss.co.uk true\nslotcar.com true\nslowfood.es true\nslse.ca true\nslxh.eu true\nslxh.nl true\nslycurity.de true\nsmallchat.nl true\nsmalldata.tech true\nsmallplanet.ch true\nsmares.de true\nsmart-ov.nl true\nsmartairkey.com true\nsmartcleaningcenter.nl true\nsmartftp.com true\nsmarthdd.com true\nsmarthomedna.com true\nsmarthouse.de true\nsmartit.pro true\nsmartlocksmith.com true\nsmartmessages.net true\nsmartphone.continental.com false\nsmartpolicingplatform.com true\nsmartrak.co.nz true\nsmartshiftme.com true\nsmartship.co.jp true\nsmartsparrow.com true\nsmartwurk.nl true\nsmdavis.us true\nsme-gmbh.net true\nsmiatek.name true\nsmith.is true\nsmithandcanova.co.uk true\nsmittix.co.uk true\nsmksi2.com false\nsmkw.com true\nsmm.im true\nsmoo.st true\nsmoothics.com true\nsmove.sg true\nsmow.com true\nsmow.de true\nsms1.ro true\nsmvfd.info true\nsnakehosting.dk false\nsnapappointments.com true\nsnapappts.com true\nsnazel.co.uk true\nsnazzie.nl true\nsneakpod.de true\nsneakynote.com true\nsneberger.cz false\nsneezry.com true\nsnelxboxlivegold.nl true\nsnfdata.com true\nsniderman.eu.org true\nsniep.net true\nsnl.no true\nsnod.land true\nsnoozedds.com true\nsnoupon.com true\nsnow-online.com true\nsnow-online.de true\nsnowcrestdesign.com true\nsnughealth.org.uk true\nsny.no true\nso-healthy.co.uk true\nsobie.ch true\nsocial-events.net true\nsocial-media-strategies.it true\nsocialbillboard.com true\nsocialdevelop.biz true\nsocialgrowing.cl true\nsocialhams.net false\nsocialhead.io true\nsocialnous.co true\nsocialrank.com true\nsocialsecurity.gov false\nsocietyhilldance.com true\nsocioambiental.org true\nsodi.nl true\nsofort.com true\nsofortueberweisung.de true\nsoftwarebetrieb.de true\nsoftwaredesign.foundation true\nsogeek.me true\nsogravatas.net.br true\nsogutma.com.tr true\nsoia.ca true\nsoju.fi true\nsokkenhoek.nl true\nsol-3.de true\nsolar-ec.com true\nsoldecom.com true\nsoleus.nu true\nsolidfuelappliancespares.co.uk true\nsolidus.systems true\nsolihullcarnival.co.uk true\nsolihulllionsclub.org.uk true\nsoll-i.ch true\nsolomisael.com true\nsolutionhoisthire.com.au true\nsolved.tips true\nsomebodycares.org true\nsomethingnew.xyz true\nsona-gaming.com true\nsonafe.info true\nsondergaard.de true\nsonicrainboom.rocks true\nsoondy.com true\nsoph.us true\nsopheos.com true\nsoply.com true\nsoporte.cc true\nsortaweird.net true\nsorz.org true\nsos.sk true\nsosaka.ml true\nsosecu.red true\nsotar.us true\nsotiran.com true\nsotor.de true\nsouki.cz true\nsoulogic.com false\nsoumikghosh.com true\nsoundforsound.co.uk true\nsoundgasm.net true\nsoundtalks.com true\nsour.is true\nsourcebox.be true\nsourcely.net true\nsourceway.de true\nsous-surveillance.net true\nsouthernutahinfluencers.com true\nsouthside-crew.com true\nsouthworcestershiregpservices.co.uk true\nsouvik.me true\nsoved.eu true\nsovereignshare.com true\nsowncloud.de true\nsp.rw false\nspacecompute.com true\nspacedust.xyz true\nspacefish.biz true\nspaggel.nl true\nsparelib.com true\nsparkforautism.org true\nsparklebastard.com true\nspauted.com true\nspawn.cz true\nspaysy.com true\nspdf.net true\nspectrosoftware.de true\nspeculor.net true\nspeedcounter.net true\nspeedmann.de true\nspeedy.lt true\nspeedyprep.com true\nspeich.net true\nspeidel.com.tr true\nsperrstun.de true\nspherenix.org true\nspibe.is true\nspicydog.org true\nspicymatch.com true\nspideroak.com true\nspiegels.nl true\nspielcasinos.com true\nspiet.nl true\nspins.fedoraproject.org true\nspirit-dev.net true\nspiritbionic.ro true\nspitefultowel.com true\nspititout.it true\nsplikity.com true\nsplitdna.com true\nsponsortobias.com true\nsportifik.com true\nspot-events.com true\nspotifyripper.tk true\nspreadsheets.google.com true\nspreed.me true\nsprigings.com true\nspritchard.photos true\nsprueche-zum-valentinstag.de true\nsprueche-zur-geburt.info true\nsprueche-zur-hochzeit.de true\nsprueche-zur-konfirmation.de true\nspuffin.com true\nspydersec.com true\nspyprofit.ru true\nspyroszarzonis.com true\nsqlapius.net true\nsqr-training.com true\nsqshq.de true\nsquare-gaming.org true\nsquare-src.de true\nsquare.com false\nsquare.gs true\nsquareup.com false\nsquawk.cc true\nsqueezemetrics.com true\nsrc.fedoraproject.org true\nsrchub.org true\nsrevilak.net true\nsritest.io true\nsrna.sk true\nsro.center true\nsrpdb.com true\nsrrr.ca true\nsrv47.de true\nss.lv false\nssa.gov false\nssbrm.ch true\nsscd.no true\nssl-zertifikate.de true\nssl.google-analytics.com true\nssl247.co.uk true\nssl247.com.mx true\nssl247.de true\nssl247.dk true\nsslcertificaten.nl true\nsslcheck.nl true\nssldecoder.org true\nsslhosting.cz true\nsslmate.com true\nsslpoint.com true\nsslsurvey.de true\nsslzilla.de true\nssmato.me true\nssnc.org true\nsss3s.com true\nsstewartgallus.com true\nssworld.ga true\nstaack.com true\nstablelib.com true\nstabletoken.com true\nstackptr.com true\nstadionmanager.com true\nstadjerspasonline.nl true\nstadtbauwerk.at true\nstage-props-blank-guns.com false\nstage.wepay.com false\nstageirites.fr true\nstagingjobshq.com true\nstagstickets.co.uk true\nstahl.xyz true\nstalder.work true\nstalker-shop.com true\nstalkerhispano.com true\nstamkassa.nl true\nstanandjerre.org true\nstandardssuck.org true\nstandingmist.com true\nstar-citizen.wiki true\nstarapple.nl true\nstarcomproj.com true\nstardanceacademy.net true\nstargatepartners.com true\nstarmusic.ga true\nstarpeak.org true\nstarsam80.net true\nstarstreak.net true\nstash.ai true\nstat.ink true\nstate-sponsored-actors.net true\nstatecover.com.au true\nstateofexception.io true\nstatic.or.at true\nstatic.wepay.com false\nstaticisnoise.com true\nstationary-traveller.eu true\nstationaryjourney.com true\nstationnementdenuit.ca true\nstatuschecks.net true\nstatuscode.ch true\nstay.black true\nstayokhotelscdc-mailing.com true\nstbennett.org true\nstderr.cc true\nsteakovercooked.com true\nstealsaga.net true\nsteamdb.info true\nsteckregal-super.de true\nsteelephys.com.au true\nstefanovski.io true\nstefanweiser.de true\nstefany.eu true\nsteidlewirt.de true\nsteigerplank.com true\nstellanova-planeten.de true\nstellenticket.de true\nstemsims.com true\nstephanierxo.com true\nstephenandburns.com true\nstereo.lu true\nstereochro.me false\nsternplastic.com true\nstesti.cz true\nstevegrav.es true\nstevenhumphrey.uk true\nstevensononthe.net true\nsteventress.com true\nstewartremodelingadvantage.com true\nstichtingsticky.nl true\nstick2bike.de true\nsticklerjs.org true\nstig.io true\nstigharder.com true\nstigroom.com true\nstillblackhat.id true\nstinkytrashhound.com true\nstirling.co false\nstirlingpoon.com false\nstirlingpoon.xyz true\nstjohnin.com true\nstjohnmiami.org true\nstkbn.com true\nstmbgr.com true\nstnl.de true\nstocktrader.com true\nstoffe-monster.de true\nstoffelen.nl true\nstoianlawfirm.com true\nstoick.me true\nstolina.de true\nstolkschepen.nl true\nstomt.com true\nstorecove.com true\nstoredsafe.com true\nstorefrontify.com true\nstormhub.org true\nstormyyd.com true\nstorvann.net true\nstorvann.no true\nstorycollective.nl true\nstpatricksguild.com true\nstrasweb.fr false\nstrbt.de true\nstrchr.com true\nstreampanel.net false\nstreamzilla.com true\nstressfreehousehold.com true\nstretchmyan.us true\nstricted.net true\nstrictlysudo.com true\nstrijkshop.be true\nstripe.com true\nstrobeltobias.de true\nstrobeto.de true\nstructurally.net true\nstrugee.net true\nstuartbell.co.uk true\nstudenckiemetody.pl true\nstudent.andover.edu true\nstudentloans.gov true\nstuder.su true\nstudienportal.eu true\nstudiozelden.com true\nstudlan.no true\nstudyhub.cf true\nstugb.de true\nstulda.cz true\nstumf.si true\nstuntmen.xyz true\nstupendous.net true\nstupus.com true\nstutelage.com true\nstuur.nl false\nstw-group.at true\nstygium.net false\nstyleci.io true\nstylenda.com true\nstyles.pm true\nsu1ph3r.io true\nsuave.io true\nsubdimension.org false\nsubeesu.com true\nsubseq.net false\nsubtitle.rip true\nsuche.org true\nsudo.ws true\nsufix.cz true\nsuian.or.jp true\nsulek.eu true\nsumoatm.com true\nsumoscout.de true\nsunbritetv.com true\nsundayfundayjapan.com true\nsuneilpatel.com true\nsunflyer.cn false\nsunnyfruit.ru true\nsunsetwx.com true\nsuos.io true\nsupastuds.com true\nsupcro.com true\nsuper-o-blog.com true\nsuperbabysitting.ch true\nsuperbart.nl true\nsuperbshare.com true\nsupereight.net true\nsuperhome.com.au true\nsuperkonsult.se true\nsupermarx.nl true\nsuperswingtrainer.com true\nsuperwally.org true\nsupplies24.at true\nsupplies24.es true\nsupport.mayfirst.org false\nsupweb.ovh false\nsurfone-leucate.com true\nsurgenet.nl true\nsurgicalassociateswny.com true\nsurkatty.org true\nsurvature.com true\nsurveypirate.com true\nsurvivalmonkey.com true\nsusanbpilates.co true\nsusanbpilates.com true\nsusastudentenjobs.de true\nsushi101tempe.com true\nsushifrick.de true\nsustainability.gov true\nsustsol.com true\nsv-turm-hohenlimburg.de true\nsvager.cz true\nsvallee.fr false\nsvarovani.tk true\nsvatba-frantovi.cz true\nsveneckelmann.de true\nsvenskacasino.com true\nsvijet-medija.hr true\nswaggerdile.com true\nswaleacademiestrust.org.uk true\nswansdoor.org true\nswapadoodle.com true\nsway-cdn.com true\nsway.com true\nswedishhost.com true\nswedishhost.se true\nsweetll.me true\nsweetstreats.ca true\nswehack.org false\nswift-devedge.de true\nswimming.ca true\nswimturk.com.tr true\nswiss-cyber-experts.ch true\nswite.com true\nswyn.net true\nsy-anduril.de true\nsycamorememphis.org false\nsychov.pro true\nsyezd.com.au true\nsykepleien.no true\nsylaps.com true\nsylvaindurand.org true\nsylvan.me true\nsylvanorder.com true\nsynabi.com true\nsynapticconsulting.co.uk true\nsynatra.co true\nsync-it.no true\nsyncappate.com true\nsynchrocube.com true\nsyncmylife.net true\nsyncserve.net true\nsynfin.org true\nsynony.me true\nsyntaxnightmare.com true\nsyriatalk.biz true\nsyriatalk.org true\nsyrocon.ch true\nsysadmin.xyz true\nsysadmins.ro true\nsysctl.se false\nsysdb.io true\nsysmike.de true\nsysmike.net true\nsyso.name true\nsyss.de true\nsystem.is true\nsystemintegra.ru true\nsystemreboot.net true\nsyzygy-tables.info true\nszagun.net true\nszaszm.tk true\nszaydon.me false\nszechenyi2020.hu true\nszentistvanpt.sk true\nszongott.net true\nt-hawk.com true\nt-point.eu true\nt-shirts4less.nl true\nt0dd.eu false\nt23m-navi.jp false\nt3rror.net true\nt7e.de true\ntaabe.xyz true\ntabelfirme.ro true\ntabla-periodica.com true\ntablotv.com false\ntaborsky.cz true\ntacomafia.net true\ntacticalsquare.com true\ntadigitalstore.com true\ntahf.net true\ntailpuff.net true\ntails.com.ar true\ntake1give1.com true\ntaken.pl true\ntakeshifujimoto.com true\ntakk.pl true\ntakkaaaaa.com true\ntakusan.ru true\ntalado.gr false\ntalentcast.nl true\ntalideon.com false\ntalk.google.com true\ntalkgadget.google.com true\ntalklifestyle.nl true\ntalktwincities.com true\ntallr.se true\ntalsi.eu true\ntamasszabo.net true\ntangibilizing.com true\ntangiblesecurity.com true\ntaniesianie.pl true\ntankski.co.uk true\ntannenhof-moelln.de true\ntantalos.nl true\ntante-bugil.net true\ntantotiempo.de true\ntapfinder.ca true\ntappublisher.com true\ntaquilla.com true\ntaravancil.com true\ntartaneagle.org.uk false\ntartaros.fi true\ntas2580.net true\ntaskforce512.de true\ntaskotron.fedoraproject.org true\ntaskotron.stg.fedoraproject.org true\ntaskstats.com true\ntaskstream.com true\ntaskulu.com true\ntasmansecurity.com true\ntastycake.net true\ntastyyy.co true\ntatilbus.com false\ntatort-fanpage.de true\ntavoittaja.fi true\ntaxaroo.com true\ntaxbench.com true\ntaxisafmatosinhos.pt true\ntaxspeaker.com true\ntaxsquirrel.com true\ntazemama.biz true\ntazj.in false\ntbitc.ch true\ntbrss.com true\ntbspace.de true\ntc-bonito.de true\ntcdw.net true\ntcgrepublic.com true\ntcomms.org true\ntdelmas.eu true\ntdelmas.ovh true\ntdrs.info true\nteabagdesign.co.uk true\nteachercreatedmaterials.com true\nteachforcanada.ca true\nteam-bbd.com true\nteam-one.racing true\nteamblueridge.org true\nteamnorthgermany.de true\nteampaddymurphy.ie true\nteampoint.cz true\nteamsocial.co true\nteamtouring.net true\nteamupturn.com true\nteamzeus.cz true\nteasenetwork.com true\ntecart-cloud.de true\ntecart-system.de true\ntecartcrm.de true\ntech-clips.com true\ntech-essential.com true\ntech-rat.com true\ntech-seminar.jp true\ntech55i.com true\ntechandtux.de true\ntechassist.io true\ntechcavern.ml true\ntechcentric.com false\ntechelements.co true\ntechhipster.net true\ntechmajesty.com true\ntechnoparcepsilon.fr true\ntechnosavvyport.com true\ntechnotonic.com.au false\ntechorbiter.com true\ntechpivot.net true\ntechpointed.com true\ntechvalue.gr true\ntecnogaming.com true\ntecture.de true\nteddy.ch true\ntedeh.net true\nteebeedee.org true\nteemo.gg true\nteemperor.de true\ntehotuotanto.net true\ntehrabbitt.com false\nteknologi.or.id true\ntelefisk.org true\ntelefonkonferenz.ch true\ntelekollektiv.org true\nteleogistic.net true\ntelescam.com true\ntellingua.com true\ntemizmama.com true\ntemp.pm true\ntempcraft.net true\ntempus-aquilae.de true\ntendertool.nl true\ntenenz.com true\ntengroup.com true\ntenni.xyz true\ntennisadmin.com true\ntensionup.com true\ntent.io true\ntentations-voyages.com true\ntenyx.de true\nteodio.cl true\nteos.online true\nteoskanta.fi true\ntepid.org true\nteriiphotography.com true\nterrab.de true\nterracloud.de true\nterraelectronica.ru true\nterraform.io true\nterrastaffinggroup.com true\nterravirtua.com true\nterraweb.net true\nterrax.net true\nterrty.net true\ntescoirelandpayslips.com true\ntesoro.pr true\ntestadren.com true\ntestingbot.com false\ntestnode.xyz true\ntestsuite.org true\ntetsumaki.net true\nteuniz.nl true\nteunstuinposters.nl true\ntextburst.com true\ntexte-zur-taufe.de true\ntexter-linz.at true\ntexterseo.at true\ntextoplano.xyz true\ntextualapp.com true\ntexy.info true\ntezcam.tk true\ntf-network.de true\ntf2b.com true\ntf2stadium.com true\ntfcoms-sp-tracker-client.azurewebsites.net true\ntfl.lu true\ntfnapps.de true\ntgr.re true\nth-bl.de true\nth.search.yahoo.com false\nthackbarth.net true\nthaicyberpoint.com true\nthaihostcool.com true\nthca.ca true\nthe-construct.com true\nthe-earth-yui.net true\nthe-gist.io true\nthe-paddies.de true\ntheamateurs.net true\ntheamp.com true\ntheberkshirescompany.com true\nthebigdatacompany.com true\nthebigwave.de true\nthebikeinsurer.co.uk true\nthebimhub.com true\nthebreakroom.org true\nthecandidforum.com true\nthecapitalbank.com true\nthecharlestonwaldorf.com true\nthecitizens.com true\nthecitywarehouse.clothing true\ntheclementinebutchers.com true\nthecloudmigrator.com true\ntheclubjersey.com true\nthecustomizewindows.com true\nthedark1337.com true\nthedevrycommonsbrasil.com true\nthedisc.nl true\nthedreamtravelgroup.co.uk true\ntheendofzion.com true\ntheescapistswiki.com true\ntheeyeopener.com true\nthefarbeyond.com true\nthefox.co true\nthefrozenfire.com false\nthegcccoin.com true\nthego2swatking.com false\nthegoldregister.co.uk true\nthegraciousgourmet.com true\nthegvoffice.net true\nthehackerblog.com true\nthehiddenbay.eu true\nthehotfix.net true\ntheinitium.com true\ntheintercept.com true\ntheitsage.com true\nthejserver.de true\nthekelvinliu.com false\nthekingofhate.com true\nthelaimlife.com true\nthelapine.ca true\nthelastsurprise.com true\nthelinuxspace.com true\nthelittlecraft.com true\nthelocals.ru true\nthemarshallproject.org true\nthemeaudit.com true\nthemoderate.xyz true\nthemoep.at true\nthemostexpensiveworkofart.com true\nthenocman.com true\nthenorthschool.org.uk true\ntheodorejones.info false\ntheojones.name false\nthepaymentscompany.com true\nthepb.in true\nthephonecaseplace.com true\nthepiratebay.poker true\ntheploughharborne.co.uk true\ntheramo.re true\ntherapynotes.com true\ntherewill.be true\ntherockawaysny.com true\nthescientists.nl true\ntheseed.io true\ntheseletarmall.com true\ntheseoframework.com true\nthesession.org false\ntheshadestore.com true\nthesharepointfarm.com true\ntheshopally.com true\nthesled.net true\nthestagchorleywood.co.uk true\nthestory.ie true\nthetechnical.me true\nthetradinghall.com true\nthetuxkeeper.de true\ntheunitedstates.io true\ntheweilai.com false\nthewhitehat.club true\nthewhitneypaige.com true\nthewindow.com true\ntheworldsend.eu true\ntheyosh.nl true\nthezero.org true\nthibautcharles.net true\nthierfreund.de true\nthierryhayoz.ch true\nthinkcoding.de true\nthinkcoding.org true\nthinklikeanentrepreneur.com true\nthinktux.net true\nthinlyveiledcontempt.com true\nthirdpartytrade.com true\nthisisacompletetest.ga true\nthisisforager.com true\nthisserver.dontexist.net true\nthiswebhost.com false\nthkb.net true\nthom4s.info true\nthomas-grobelny.de true\nthomasbreads.com true\nthomasgriffin.io true\nthomashunter.name false\nthomaskliszowski.fr false\nthomasnet.fr true\nthomastimepieces.com.au true\nthomspooren.nl true\nthomwiggers.nl true\nthorbis.com true\nthorbiswebsitedesign.com true\nthorgames.nl true\nthouni.de true\nthreatcentral.io true\nthreedpro.me true\nthreelions.ch true\nthroughthelookingglasslens.co.uk true\nthrowaway.link true\nthrowpass.com true\nthrx.net true\nthusoy.com true\nthyngster.com false\ntiacollection.com true\ntianshili.me true\nticketmates.com.au true\nticketoplichting.nl true\ntickettoaster.de true\ntickreport.com true\ntid.jp false\ntidycustoms.net true\ntiendavertigo.com true\ntiendschuurstraat.nl true\ntiens-ib.cz true\ntifan.net true\ntiffnix.com true\ntigerchef.com true\ntigerdile.com true\ntikutiku.pl true\ntilikum.io true\ntillseasyscore.com true\ntimbuktutimber.com true\ntimcamara.com true\ntimersuite.com true\ntimmersgems.com true\ntimmy.ws true\ntimtaubert.de true\ntimvivian.ca true\ntimwittenberg.com true\ntinastahlschmidt.de true\ntinfoilsecurity.com false\ntinkertry.com false\ntinte24.de true\ntintenfix.net true\ntinylan.com true\ntinyvpn.org true\ntiplanet.org true\ntipps-fuer-den-haushalt.de true\ntippspiel.cc true\ntitanous.com true\ntjenestetorvet.dk true\ntlach.cz true\ntlo.link true\ntlo.xyz true\ntls.builders true\ntls.care true\ntls1914.org true\ntmaward.net true\ntmf.ru true\ntmi-products.eu true\ntmi-produkter.se true\ntmpraider.net true\ntmpsantos.com.br true\ntmtopup.com true\ntncnanet.com.br true\ntnes.dk true\ntno.io true\ntobias-kluge.de true\ntobiasmathes.com true\ntobiasmathes.name true\ntobiassachs.de true\ntobiassattler.com true\ntoccoig.com true\ntodesschaf.org true\ntodo.is true\ntodoist.com true\ntofu.im true\ntogelonlinecommunity.com true\ntokaido.com true\ntokke.dk true\ntokoone.com false\ntokyo-powerstation.com true\ntollsjekk.no true\ntom.horse true\ntomasjacik.cz true\ntomask.info true\ntomaspialek.cz true\ntomatenaufdenaugen.de true\ntomaw.net true\ntombaker.me true\ntombrossman.com true\ntomcort.com true\ntomeara.net true\ntomfisher.eu true\ntomli.me true\ntommsy.com true\ntommyads.com true\ntomo.gr false\ntomrei.com true\ntomrichards.net true\ntomudding.nl true\ntomvote.com true\ntomwiggers.nl false\ntomwilson.io true\ntonabor.ru true\ntonage.de true\ntoncusters.nl true\ntonegidoarchief.nl false\ntoner24.at true\ntoner24.co.uk true\ntoner24.es true\ntoner24.fr true\ntoner24.it true\ntoner24.nl true\ntoner24.pl true\ntonerdepot.de true\ntonerjet.at true\ntonerjet.co.uk true\ntonerklick.de true\ntonerkurier.de true\ntonermaus.de true\ntonermonster.de true\ntonex.de true\ntonex.nl true\ntonkinson.com true\ntonsit.com true\ntonsit.org true\ntonyfantjr.com true\ntonymanning.com false\ntonytan.cn true\ntonytan.io true\ntonywebster.com true\ntoolbox.ninja true\ntoomanypillows.com true\ntop-stage.net true\ntopbrakes.com true\ntopdevbox.net true\ntopfivepercent.co.uk true\ntopmarine.se true\ntopnewstoday.org true\ntopnovini.com true\ntoptexture.com true\ntoptranslation.com true\ntopyx.com true\ntorahanytime.com true\ntormentedradio.com true\ntorproject.org false\ntorprojects.com true\ntorquato.de false\ntorrent.fedoraproject.org true\ntorrent.is true\ntorrenttop100.net true\ntorsten-schmitz.net true\ntorv.rocks true\ntoshkov.com true\ntoshnix.com true\ntosteberg.se true\ntotalbeauty.co.uk true\ntotalcarcheck.co.uk true\ntotalchecklist.com true\ntotaltriathlon.com true\ntotch.de true\ntotem-international.com true\ntouch.facebook.com false\ntouch.mail.ru true\ntouchstonefms.co.uk true\ntouhou.cc true\ntourispo.com true\ntout-art.ch true\ntoutart.ch true\ntoverland-tickets.nl true\ntowandalibrary.org true\ntownhousedevelopments.com.au true\ntox.im true\ntoysperiod.com true\ntp-iryuubun.com true\ntp-kabushiki.com true\ntp-kyouyufudousan.com true\ntp-law.jp true\ntpbcdn.com true\ntpbproxy.co true\ntr.search.yahoo.com false\ntraas.org true\ntrabbel.org true\ntrackchair.com true\ntrade-smart.ru true\ntradeacademy.in true\ntradedesk.co.za true\ntradeinvent.co.uk true\ntrademan.ky true\ntradiz.org true\ntrainex.org true\ntrakfusion.com true\ntranos.de true\ntransacid.de true\ntransdirect.com.au true\ntransfers.do true\ntransfigurewizard.com true\ntransformify.org true\ntransitpoint.us true\ntranslate.fedoraproject.org true\ntranslate.googleapis.com true\ntranslate.stg.fedoraproject.org true\ntransportal.sk true\ntransverify.com true\ntrashnothing.com true\ntrauertexte.info true\ntravador.com true\ntravelinsurance.co.nz true\ntravisf.net true\ntreebaglia.xyz true\ntreeby.net true\ntreeschat.com true\ntrell.co.in true\ntrendkraft.de true\ntresorit.com true\ntresorsecurity.com true\ntretkowski.de true\ntribaldos.com true\ntribut.de true\ntrident-online.de true\ntrik.es true\ntrim-a-slab.com true\ntrimage.org true\ntrineco.com true\ntrineco.fi true\ntrinnes.net true\ntriop.se true\ntriple-mmm.de true\ntripseats.com true\ntroi.de true\ntrollme.me true\ntrommelwirbel.com true\ntrophee-discount.com true\ntruckerswereld.nl true\ntruckstop-magazin.de true\ntrueblueessentials.com true\ntruebred-labradors.com true\ntruejob.com true\ntruestaradvisors.com true\ntrueteaching.com true\ntrufflemonkey.co.uk true\ntrunkjunk.co true\ntruserve.org true\ntrusitio.com true\ntrustedinnovators.com true\ntrusteecar.com true\ntrustmeimfancy.com true\ntruthmessages.pw true\ntrw-reseller.com true\ntryfabulousdiet.com true\ntryfabulousskincream.com true\ntryfabulousskinserum.com true\ntryoneday.co true\ntrywesayyes.com true\ntrzepak.pl true\nts3.consulting true\ntsecy.com true\ntsgbit.net true\ntsrstore.gq true\ntssouthernpower.com true\ntsumegumi.net true\ntsumi.it true\nttcf.ca true\ntty.space true\nttz.im true\ntuamoronline.com true\ntubepro.de true\ntucny.com true\ntucuxi.org true\ntuitle.com true\ntumelum.de true\ntumutanzi.com true\ntunai.id true\ntunebitfm.de true\ntunnelblick.net true\ntuntitili.fi true\nturbobit.ch true\nturnik-67.ru true\nturniker.ru true\nturtle.ai true\nturtlementors.com true\ntuvalie.com true\ntuxcall.de true\ntuxflow.de true\ntuxgeo.com false\ntuxplace.nl true\ntuxz.net true\ntuzaijidi.com true\ntvbeugels.nl true\ntw.search.yahoo.com false\ntwaka.com true\ntwd2.me true\ntwd2.net false\ntwee-onder-een-kap-woning-in-brielle-kopen.nl true\ntwelve.rocks true\ntwelve.today true\ntwentymilliseconds.com true\ntwisto.cz true\ntwitter.com false\ntwitteroauth.com true\ntwofactorauth.org true\ntwopif.net true\ntxf.pw true\nty2u.com true\ntyche.io true\ntyl.io true\ntylerschmidtke.com true\ntypecodes.com true\ntypeonejoe.com true\ntypewolf.com true\ntyping.com true\ntypingrevolution.com true\ntypo3.com true\ntyrelius.com true\ntysye.ca true\ntzappa.net true\nu-blox.com true\nuae-company-service.com true\nuangteman.com true\nuasmi.com true\nuat-activesg.com true\nub3rk1tten.com false\nubanquity.com true\nuber.com.au true\nuberboxen.net true\nubertt.org true\nubicloud.de true\nubicv.com true\nublox.com true\nubtce.com true\nucfirst.nl true\nudomain.net true\nuerdingen.info true\nuesociedadlimitada.com true\nueu.me true\nufgaming.com true\nufotable.uk false\nuhc.gg true\nui8.net true\nuk.search.yahoo.com false\nukchemicalresearch.org true\nukdefencejournal.org.uk true\nukdropshipment.co.uk true\nukdropshipment.com true\nukhas.net true\nukrainians.ch true\nukwct.org.uk true\nulabox.cat true\nulabox.com true\nulabox.es true\nulrik.moe true\nultieme.be true\numassfive.coop true\numgardi.ca true\numisonoda.com true\numwandeln-online.de true\nun-zero-un.fr true\nunblocked-networks.org true\nunblocked.win true\nunblockmy.party true\nunblockmy.tech true\nunblockmy.xyz true\nunblockthe.site true\nunblockthe.top true\nunder30stravelinsurance.com.au true\nundernet.uy true\nundo.co.il true\nundone.me true\nunearaigneeauplafond.fr true\nunexpected.nu true\nunfiltered.nyc true\nungegamere.dk true\nunicef.pl true\nunicooo.com true\nunicredit.ba true\nunicredit.ro true\nunicreditbank.hu true\nunicreditbank.rs true\nunicreditbank.ru true\nuniekglas.nl true\nuniform-agri.com true\nunila.edu.br true\nunionplat.ru true\nunionstationapp.com true\nuniq.site true\nunirenter.ru true\nunison.com true\nunisyssecurity.com true\nunit7jazz.com true\nunit7jazz.org true\nunited.com false\nunitedadmins.com true\nunitedcyberdevelopment.com true\nunitel2000.de true\nuniversalcarremote.com true\nuniversalpaymentgateway.com true\nuniversity4industry.com true\nunixadm.org true\nunknownphenomena.net true\nuno-pizza.ru true\nunoccupyabq.org true\nunpossible.xyz true\nunpr.dk true\nunravel.ie true\nuns.vn true\nunseen.tw true\nunsystem.net true\nunterfrankenclan.de true\nunterschicht.tv true\nuntoldstory.eu true\nunun.fi true\nunwiredbrain.com true\nunyq.me false\nup1.ca true\nupani.net true\nupboard.jp true\nupitnik.rs true\nupldr.pw true\nupload.facebook.com false\nuploadbeta.com true\nupr.com.ua true\nupstox.com true\nuptic.net true\nuptimed.com true\nuptimenotguaranteed.com true\nuptrends.com true\nuptrends.de true\nur-lauber.de true\nurandom.eu.org true\nurban.melbourne true\nurbanesecurity.com true\nurbanmelbourne.info true\nuripura.de true\nurlchomp.com true\nurspringer.de true\nusaa.com false\nusaab.org true\nusabackground.com true\nusakitchensandflooring.com true\nusbcraft.com true\nusbirthcertificate.com false\nusbtypeccompliant.com true\nuscntalk.com true\nusd.de true\nuse.be true\nusercare.com true\nuseresponse.com true\nuserify.com true\nusetypo3.com true\nusgande.com true\nusimmigration.us true\nusitcolours.bg true\nuslab.io true\nusleep.net true\nusparklodging.com true\nuspsoig.gov true\nust.space true\nutilia.tools true\nutilityapi.com true\nutleieplassen.no true\nutonia.ch true\nutopiagalaxy.space true\nutopianhomespa.com true\nutopianrealms.org true\nutopians.dk true\nutopicestudios.com true\nuttnetgroup.fr false\nuvarov.pw false\nv0tti.com false\nv2.pw true\nvagrantup.com true\nvakuutuskanava.fi true\nval-sec.com true\nvalentin-sundermann.de true\nvalidbrands.com true\nvalitron.se true\nvalkohattu.fi true\nvalleyridgepta.org true\nvalopv.be true\nvalordolarblue.com.ar true\nvalshamar.is true\nvalsk.is true\nvalskis.lt true\nvaltoaho.com true\nvandalfsen.me true\nvanderkley.it true\nvanestack.com true\nvanetv.com true\nvangeluwedeberlaere.be true\nvanhoutte.be true\nvanitynailworkz.com true\nvanlaanen.com true\nvansieleghem.com true\nvantien.com true\nvantru.is true\nvapemania.eu true\nvarden.info true\nvarghese.de true\nvaricoseveinssolution.com true\nvarunagw.com false\nvarvy.com true\nvat-eu.com true\nvattulainen.fi true\nvault21.net true\nvaultproject.io true\nvavai.net true\nvazue.com true\nvbest.net true\nvbhelp.org true\nvcientertainment.com true\nvcr.re true\nvcsjones.com true\nvdbongard.com true\nvdcomp.cz true\nvdrpro.com true\nve.search.yahoo.com false\nveblen.com false\nvegalitarian.org true\nveggiesbourg.fr true\nveil-framework.com true\nvelasense.com true\nvenicerealdeal.com true\nventurepro.com true\nvereinscheck.de true\nverifiedinvesting.com true\nverifikatorindonesia.com true\nverizonguidelines.com true\nveronique-schmitz.de true\nversbeton.nl true\nversia.ru true\nversicherungskontor.net true\nveryhax.de true\nvespacascadia.com true\nvetdnacenter.com true\nvetinte.eu false\nvfdworld.com true\nvgerak.com true\nvgropp.de true\nviaprinto.de true\nviasinc.com false\nvictorcanera.com true\nvictorjacobs.com true\nvid-immobilien.de true\nvide-dressing.org true\nvide-greniers.org true\nvide-maisons.org true\nvideogamesartwork.com true\nvideomail.io true\nvideotogel.net true\nvidid.net true\nvieaw.com true\nvieclam24h.vn false\nviemeister.com true\nviewmyrecords.com true\nvigilantnow.com true\nvigilo.cf true\nvigilo.ga true\nvigo-krankenversicherung.de true\nvigo-tarife.de true\nvijos.org true\nvikasbabyworld.de true\nvikashkumar.me true\nvikings.net true\nviktorsvantesson.net true\nvilaydin.com true\nvillenvinkit.com true\nvimeo.com true\nvincentcox.com true\nvincentkooijman.at true\nvincentkooijman.nl true\nvincentpancol.com true\nvincitraining.com true\nvinilosdecorativos.net true\nvintageheartcoffee.com true\nvinyculture.com true\nvio.no true\nviperdns.com true\nviphospitality.se true\nvipmusic.ga true\nvipnettikasinoklubi.com true\nvirginiacrimeanalysisnetwork.org true\nvirtualdesignmedia.com false\nvirtuallifestyle.nl true\nvirtualperez.com true\nvirtualsanity.com true\nviscopic.com true\nvisioflux-premium.com true\nvisionarymedia.nl true\nvisionless.me false\nvisiontree-beta.eu true\nvisiontree.eu true\nvispaleistexel.nl true\nvistaalmar.es true\nvistarait.com true\nvistb.me true\nvitagenda.nl true\nvitalismaatjes.nl true\nvitalita.cz true\nvitalorange.com true\nvitapingu.de true\nvitkausk.as true\nvitrado.de true\nvitta.me true\nvivaldi.club true\nvivendi.de true\nviviotech.net false\nvjirovsky.cz true\nvkox.com true\nvleij.com true\nvleij.family true\nvleij.se true\nvmem.jp true\nvmoagents.com false\nvn.search.yahoo.com false\nvnd.cloud true\nvnvisa.center true\nvnvisa.ru true\nvocaloid.my true\nvoceinveste.com true\nvogt.tech true\nvoidi.ca true\nvokeapp.com true\nvolkden.com true\nvolker-gropp.de true\nvolkergropp.de true\nvomitb.in true\nvonavycukor.sk true\nvonedelmann.de true\nvoorjou.com true\nvop.li true\nvorlif.org true\nvorodevops.com true\nvozp.cz true\nvpl.me false\nvpn.ht true\nvpnhot.com true\nvps-szerver-berles.hu true\nvratny.space true\nvrobert.fr false\nvrtak-cz.net true\nvsean.net true\nvserver-preis-vergleich.de true\nvsund.de true\nvulnerability.ch true\nvulners.com true\nvumetric.com true\nvuosaarenmontessoritalo.fi true\nvxapps.com true\nvxstream-sandbox.com true\nvyber-odhadce.cz true\nvyberodhadce.cz true\nvyplnto.cz true\nw-spotlight.appspot.com true\nw.wiki true\nw4nvu.org true\nw4xzr.top true\nw4xzr.xyz true\nwa-stromerzeuger.de true\nwachter.biz true\nwaelti.xxx true\nwafa4hw.com true\nwaffle.at true\nwail.net true\nwait.jp true\nwaka-mono.com true\nwakamiyasumiyosi.com true\nwakened.net true\nwalkeryoung.ca true\nwalkingforhealth.org.uk true\nwallet.google.com true\nwallpapers.pub true\nwallsblog.dk true\nwalnutgaming.co.uk false\nwalnutgaming.com false\nwan.pp.ua true\nwander.al true\nwangqiliang.cn true\nwangqiliang.com true\nwangql.cn true\nwangql.net true\nwarekon.com true\nwarekon.dk true\nwarhaggis.com true\nwarmservers.com true\nwarped.com true\nwarr.ath.cx true\nwarrencreative.com false\nwarsentech.com true\nwarsh.moe true\nwartorngalaxy.com true\nwasema.com true\nwasserspucker.de true\nwatchium.com true\nwatchweasel.com true\nwaterfedpole.com true\nwatersb.org true\nwave.is true\nwavefrontsystemstech.com true\nwavesboardshop.com true\nwaylaydesign.com true\nwayohoo.com true\nwayohoo.net true\nwaze.com true\nwbg-vs.de true\nwbt-solutions.ch true\nwbt-solutions.net true\nwdbgroup.co.uk true\nwdesk.com true\nwdt.cz true\nwealthcentral.com.au true\nwealthprojector.com true\nwealthprojector.com.au true\nwealthreport.com.au true\nwear2work.nl true\nwearandcare.net true\nwearegenki.com true\nwearvr.com true\nweather-and-climate.com true\nweathermyway.rocks true\nweaverhairextensions.nl true\nweb-hotel.gr true\nweb-torrent.com true\nweb.cc true\nwebandmore.de false\nwebandwords.com.au true\nwebapps.directory true\nwebchat.domains true\nwebcollect.org.uk true\nwebdesign-kronberg.de true\nwebdesigneauclaire.com true\nwebdev.mobi true\nwebdevops.io true\nwebeau.com true\nwebelement.sk true\nwebergrillrestaurant.com true\nwebfilings-eu-mirror.appspot.com true\nwebfilings-eu.appspot.com true\nwebfilings-mirror-hrd.appspot.com true\nwebfilings.appspot.com true\nweblogic.pl true\nweblogzwolle.nl true\nwebmail.gigahost.dk false\nwebmail.onlime.ch false\nwebmail.schokokeks.org false\nwebmaniabr.com true\nwebmarketingfestival.it true\nwebmax.com.tr true\nwebmedpharmacy.co.uk true\nwebnosql.com true\nwebogram.org true\nwebrebels.org true\nwebs4all.ro true\nwebscale.nl true\nwebsectools.com true\nwebseitendesigner.com false\nwebseitenserver.com true\nwebsenat.de true\nwebstore.be true\nwebstory.xyz true\nwebstudio-n.com true\nwebstylemedia.com true\nwebtalis.nl true\nwebtasarim.pw true\nwebtheapp.com true\nwebtiles.co.uk true\nwebtobesocial.de true\nwebtorrent.io true\nwebtrh.cz true\nwebwit.nl true\nwebwork.pw true\nwebyazilimankara.com true\nwebzanem.com true\nweeblr.com true\nweekdone.com true\nweekly-residence.com true\nwefinanceinc.com true\nweggeweest.nl true\nwegner.no true\nweicn.org true\nweirdesigns.com true\nweisse-liste.de true\nwelches-kinderfahrrad.de true\nwelcomehelp.de true\nwellacapability.com true\nwellastore.ru true\nwelldrake.com true\nwellensteyn.ru true\nwelovemail.com true\nwelpy.com false\nwelteneroberer.de true\nweltengilde.de true\nweltenhueter.de true\nweltmeisterschaft.net true\nweme.eu true\nwenjs.me true\nwepay.com false\nwepay.in.th true\nwer-kommt-her.de true\nwerally.com true\nwerbewelt-tv.de true\nwerken-bij-inwork.nl true\nwerkenbijkfc.nl true\nwerktor.net true\nwesecom.com false\nwesleycabus.be true\nwessner.org true\nwestcountrystalking.com true\nwesteros.hu true\nwestsuburbanbank.com true\nwestsussexconnecttosupport.org true\nwetofu.top true\nwetoxic.com true\nwetthost.com true\nwevolver.com true\nwf-bigsky-master.appspot.com true\nwf-demo-eu.appspot.com true\nwf-demo-hrd.appspot.com true\nwf-dogfood-hrd.appspot.com true\nwf-hosting.de true\nwf-pentest.appspot.com true\nwf-staging-hr.appspot.com true\nwf-training-hrd.appspot.com true\nwf-training-master.appspot.com true\nwf-trial-hrd.appspot.com true\nwfh.ovh true\nwfh.se true\nwftda.com true\nwhatanime.ga true\nwhatnext.limited true\nwhatsapp.com true\nwhatsmychaincert.com true\nwhatsstalk.me true\nwhatsupgold.com.tw true\nwhatwg.org true\nwhd-guide.de true\nwheeler.kiwi.nz true\nwhen-release.ru false\nwhen.fm true\nwherephoto.com false\nwhey-protein.ch true\nwhiskeyriver.co.uk true\nwhiskynerd.ca true\nwhisp.ly true\nwhispeer.de true\nwhistleb.com true\nwhitehouse.gov true\nwhiterabbitcakery.com true\nwhiteroom.agency true\nwhocalld.com true\nwhocalled.us true\nwholebites.com true\nwholesomeharvestbread.com true\nwholikes.us true\nwhonix.org true\nwiberg.nu true\nwideboxmacau.com false\nwidemann.de true\nwidememory.com true\nwiedu.net true\nwieninternational.at true\nwifimask.com true\nwifirst.net true\nwiimotion.de true\nwijnservices.nl true\nwiki.python.org true\nwikibooks.org true\nwikiclash.info false\nwikidata.org true\nwikidsystems.com false\nwikimediafoundation.org true\nwikimilk.org true\nwikinews.org true\nwikipedia.org true\nwikiquote.org true\nwikisource.org true\nwikiversity.org true\nwikivoyage.org true\nwiktionary.org true\nwiktoriaslife.com true\nwilddog.com true\nwillberg.bayern true\nwillcipriano.com true\nwilliamfeely.info true\nwilliamsapiens.com true\nwilliamsonshore.com true\nwillnorris.com true\nwillosagiede.com true\nwills.co.tt true\nwimake.solutions true\nwind.moe true\nwindhaven.nl true\nwindrunner.se true\nwindscribe.com true\nwinebid.com true\nwinecodeavocado.com true\nwinghill.com true\nwingumd.net true\nwinhistory-forum.net true\nwinmodels.org true\nwinmodels.ru true\nwinpack.cf true\nwinpack.eu.org true\nwinter.engineering false\nwinterfeldt.de true\nwintodoor.com true\nwipply.com false\nwirc.gr true\nwiretrip.io true\nwirkstoffreich.de true\nwis.no true\nwispapp.com true\nwisv.ch true\nwisweb.no true\nwitae.com true\nwithgoogle.com true\nwithinsecurity.com true\nwithmy.beer true\nwithyoutube.com true\nwittcher.com true\nwittydonut.com true\nwitway.nl false\nwizzley.com false\nwizzr.nl true\nwje-online.de true\nwjglerum.nl true\nwkv.com true\nwlaws.com true\nwlzhiyin.cn true\nwm-talk.net true\nwmcuk.net true\nwnu.com true\nwo2forum.nl true\nwodice.com true\nwodka-division.de true\nwofford-ecs.org true\nwohlgemuth.rocks false\nwohnsitz-ausland.com true\nwolfachtal-alpaka.de true\nwolfemg.com true\nwolfsden.cz true\nwolfwings.us true\nwomb.city true\nwomf.org true\nwonder.com.mx true\nwonderhost.info true\nwonderlandmovies.de true\nwondermags.com true\nwondershift.biz true\nwondy.com true\nwoodbury.io true\nwoodlandschurch.net true\nwoodlandsmetro.church true\nwoodomat.com true\nwoording.com true\nwootton95.com true\nworcesterfestival.co.uk true\nword-grabber.com true\nwordpress.com false\nwordsmart.it true\nwordxtra.net true\nworesite.jp true\nworkingclassmedia.com true\nworkray.com true\nworldcubeassociation.org true\nwoutergeraedts.nl true\nwoutervdb.com true\nwow-foederation.de true\nwownmedia.com true\nwp-tao.com true\nwpac.de true\nwpfortify.com true\nwphostingblog.nl true\nwpletter.de false\nwpmeetup-berlin.de true\nwpostats.com true\nwpserp.com true\nwpvulndb.com true\nwql.zj.cn true\nwrara.org true\nwrgms.com true\nwrightdoumawedding.com true\nwrldevelopment.com true\nwrwg.ca true\nwsa.poznan.pl true\nwscales.com true\nwsdezign.com true\nwss.com.ve true\nwsv-grafenau.de true\nwtfismyip.com true\nwth.in true\nwubocong.com true\nwubthecaptain.eu true\nwuetix.de true\nwufu.org true\nwunderkarten.de true\nwunderlist.com true\nwundi.net true\nwurzelzwerg.net true\nwvg.myds.me true\nwvr-law.de false\nww2onlineshop.com false\nwww.aclu.org false\nwww.airbnb.com true\nwww.banking.co.at false\nwww.capitainetrain.com false\nwww.captaintrain.com false\nwww.cloudflare.com false\nwww.cyveillance.com true\nwww.dropbox.com true\nwww.dropcam.com false\nwww.entropia.de false\nwww.eternalgoth.co.uk true\nwww.etsy.com true\nwww.evernote.com false\nwww.facebook.com false\nwww.fastmail.com true\nwww.gamesdepartment.co.uk false\nwww.getcloak.com false\nwww.gmail.com false\nwww.googlemail.com false\nwww.gov.uk false\nwww.gpo.gov false\nwww.grc.com false\nwww.healthcare.gov false\nwww.heliosnet.com true\nwww.honeybadger.io false\nwww.icann.org false\nwww.intercom.io false\nwww.irccloud.com false\nwww.lastpass.com false\nwww.ledgerscope.net false\nwww.linode.com false\nwww.lookout.com false\nwww.makeyourlaws.org true\nwww.mydigipass.com false\nwww.mylookout.com false\nwww.noisebridge.net false\nwww.opsmate.com true\nwww.paypal.com false\nwww.python.org true\nwww.rememberthemilk.com true\nwww.schokokeks.org true\nwww.simbolo.co.uk false\nwww.simple.com false\nwww.therapynotes.com true\nwww.tinfoilsecurity.com false\nwww.torproject.org false\nwww.twitter.com false\nwww.united.com false\nwww.usaa.com false\nwww.viasinc.com true\nwww.vino75.com false\nwww.wepay.com false\nwww.wordpress.com false\nwxcafe.net true\nwy6.org true\nwyeworks.com true\nwynterhill.co.uk true\nwzrd.in true\nwzyboy.org true\nx.io true\nx.st true\nx3led.com true\nx509.io true\nx64architecture.com true\nxa1.uk true\nxalqbank-online.az true\nxatr0z.org false\nxbb.hk true\nxbb.li true\nxbind.io true\nxboxdownloadthat.com true\nxboxlivegoldshop.nl true\nxbrlsuccess.appspot.com true\nxbt.co true\nxcentricmold.com true\nxd.cm true\nxdd.io true\nxdeftor.com true\nxendo.net true\nxetown.com true\nxett.com true\nxf-liam.com true\nxfive.de true\nxg3n1us.de true\nxgclan.com true\nxho.me true\nxiangweiqing.co.uk true\nxiaofengsky.com true\nxiaoguo.net true\nxiaolan.me true\nxichtsbuch.de true\nxichuangke.com true\nxicreative.net true\nxilef.org true\nximage.me false\nxinbiji.cn true\nxkviz.net true\nxmerak.com true\nxmpp.dk true\nxmppwocky.net true\nxmr.to true\nxn--3lqp21gwna.cn true\nxn--3lqp21gwna.xn--fiqs8s true\nxn--3lqt7ir4md4tzwa.cn true\nxn--3lqt7ir4md4tzwa.xn--fiqs8s true\nxn--4dbjwf8c.cf true\nxn--4dbjwf8c.ga true\nxn--4dbjwf8c.ml true\nxn--4dbjwf8c.tk true\nxn--7xa.google.com true\nxn--80aaihqncaejjobbu6v.xn--p1ai true\nxn--9pr52k0p5a.com true\nxn--datenrettung-mnchen-jbc.com true\nxn--hfk-allgu-schwaben-stb.de true\nxn--jobbrse-d1a.de true\nxn--jp-6l5cs1yf3ivjsglphyv.net true\nxn--knstler-n2a.tips false\nxn--lgb3a8bcpn.cf true\nxn--lgb3a8bcpn.ga true\nxn--lgb3a8bcpn.gq true\nxn--lgb3a8bcpn.ml true\nxn--ls8hi7a.tk true\nxn--mgbbh2a9fub.xn--ngbc5azd true\nxn--pbt947am3ab71g.com true\nxn--qckss0j.tk true\nxn--t8j4aa4nyhxa7duezbl49aqg5546e264d.net true\nxn--u9jv84l7ea468b.com true\nxnode.org false\nxoffy.com true\nxolphin.nl true\nxotika.tv true\nxpd.se true\nxps2pdf.co.uk true\nxqin.net true\nxrockx.de true\nxsmobile.de true\nxss.ht true\nxss.sk true\nxtremegaming.it true\nxtrim.ru true\nxtronics.com true\nxuc.me true\nxuexb.com true\nxunn.io true\nxuntier.ch true\nxwalck.se true\nxwaretech.info true\nxyfun.net true\nyacobo.com true\nyafuoku.ru true\nyagihiro.tech true\nyahvehyireh.com true\nyak.is true\nyakmade.com true\nyakmoo.se true\nyamadaya.tv true\nyamaken.jp true\nyamamo10.com true\nyameveo.com true\nyanaduday.com true\nyanovich.net true\nyanwh.xyz true\nyaoidreams.com true\nyapbreak.fr true\nyaporn.tv false\nyard-fu.com true\nyatesun.com true\nyaucy.win true\nyawnbox.com true\nyaxim.org true\nycc.wtf true\nyclan.net true\nydy.jp true\nyecl.net true\nyello.website true\nyenniferallulli.com true\nyesonline.asia true\nyesonline.me true\nyetcore.io true\nyetzt.me false\nyhaupenthal.org true\nyikzu.cn true\nyinlei.org true\nyippie.nl true\nyjsw.sh.cn true\nyksityisyydensuoja.fi true\nymarion.de true\nynode.co true\nynode.com true\nynsn.nl true\nyobst.tk true\nyoga.is-an-engineer.com true\nyolobert.de true\nyombo.net true\nyoramvandevelde.net true\nyorcom.nl true\nyorname.ml true\nyosemo.de true\nyoucancraft.de true\nyoucontrol.ru true\nyoudowell.com true\nyoukaryote.com true\nyoukaryote.org true\nyoukok2.com true\nyourself.today true\nyourznc.com true\nyoutous.me true\nyouyoulemon.com true\nypart.eu true\nypcs.fi true\nypiresia.fr true\nyplanapp.com true\nys-shop.biz true\nytvwld.de true\nyuan.ga true\nyufan.me true\nyuhen.ru false\nyuko.moe true\nyunity.org true\nyunzhu.li true\nyusa.me true\nyutabon.com false\nyutangyun.com true\nyvesx.com true\nyyyy.xyz true\nyzal.io true\nz-vector.com true\nz.ai true\nz0rro.net true\nz1h.de true\nz33.ch true\nzaalleatherwear.nl true\nzacarias.com.ar true\nzachpeters.org true\nzadieheimlich.com true\nzadroweb.com true\nzahe.me true\nzakmccrac.de true\nzalan.do true\nzamorano.edu true\nzaoshanghao-dajia.rhcloud.com true\nzap.yt true\nzapier.com true\nzaufanatrzeciastrona.pl true\nzbasenem.pl true\nzbigniewgalucki.eu true\nzbp.at true\nzbrane-doplnky.cz true\nzcarot.com false\nzcarrot.com true\nzcon.nl true\nzdbl.de true\nzdrojak.cz true\nze3kr.com true\nzebry.nl true\nzeedroom.be true\nzefu.ca true\nzehdenick-bleibt-bunt.de true\nzehntner.ch true\nzeitzer-turngala.de true\nzellari.ru true\nzen-trader.com false\nzenithmedia.ca true\nzeno-system.com true\nzentraler-kreditausschuss.de true\nzenvideocloud.com true\nzeplin.io true\nzer0-day.pw true\nzerekin.net true\nzerocool.io true\nzerolab.org true\nzeroling.com true\nzeropush.com true\nzertif.info true\nzerudi.com true\nzespia.tw true\nzeto365.pl true\nzetorzeszow.pl true\nzett4.me true\nzettaplan.ru true\nzeytin.pro true\nzgrep.org true\nzhang-hao.com true\nzhanghao.me true\nzhangruilin.com true\nzhangyuhao.com true\nzhaofeng.li true\nzhh.in true\nzhihua-lai.com true\nzhovner.com true\nzicklam.com true\nzifb.in true\nzima.io true\nzimiao.moe true\nzingarastore.com true\nzionvps.com false\nzippy-download.com true\nzippy-download.de true\nzixiao.wang true\nzizoo.com true\nzju.tv true\nzjubtv.com true\nzjutv.com true\nzlatosnadno.cz true\nzlavomat.sk true\nzmy.im true\nznation.nl true\nzoe.vc true\nzoeller.me true\nzohar.wang true\nzombiesecured.com true\nzoneminder.com true\nzonglovani.info true\nzoomingin.net true\nzoommailing.com true\nzooom.azurewebsites.net true\nzooparadies.eu true\nzorium.org true\nzorntt.fr true\nzortium.report true\nzorz.info true\nzotero.org true\nzravypapir.cz true\nzscales.com true\nztcaoll222.cn true\nzulu7.com true\nzundapp529.nl true\nzuram.net true\nzvncloud.com true\nzvps.uk true\nzwerimex.com true\nzx6rninja.de true\nzyf.pw true\nzzsec.org true\nzzw.ca true\n"
  },
  {
    "path": "src/html.cc",
    "content": "/*\n * File: html.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Dillo HTML parsing routines\n */\n\n/*-----------------------------------------------------------------------------\n * Includes\n *---------------------------------------------------------------------------*/\n#include <ctype.h>      /* for isspace */\n#include <string.h>     /* for memcpy and memmove */\n#include <stdlib.h>\n#include <stdio.h>      /* for sprintf */\n#include <errno.h>\n\n#include \"bw.h\"         /* for BrowserWindow */\n#include \"msg.h\"\n#include \"binaryconst.h\"\n#include \"colors.h\"\n#include \"html_charrefs.h\"\n#include \"utf8.hh\"\n\n#include \"misc.h\"\n#include \"uicmd.hh\"\n#include \"history.h\"\n#include \"menu.hh\"\n#include \"prefs.h\"\n#include \"capi.h\"\n#include \"html.hh\"\n#include \"html_common.hh\"\n#include \"form.hh\"\n#include \"table.hh\"\n\n#include \"dw/textblock.hh\"\n#include \"dw/bullet.hh\"\n#include \"dw/listitem.hh\"\n#include \"dw/image.hh\"\n#include \"dw/ruler.hh\"\n\n/*-----------------------------------------------------------------------------\n * Defines\n *---------------------------------------------------------------------------*/\n\n/* Define to 1 to ignore white space immediately after an open tag,\n * and immediately before a close tag. */\n#define SGML_SPCDEL 0\n\n#define TAB_SIZE 8\n\n/*-----------------------------------------------------------------------------\n * Name spaces\n *---------------------------------------------------------------------------*/\nusing namespace lout;\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::ui;\nusing namespace dw::core::style;\n\n/*-----------------------------------------------------------------------------\n * Typedefs\n *---------------------------------------------------------------------------*/\nclass DilloHtml;\ntypedef void (*TagOpenFunct) (DilloHtml *html, const char *tag, int tagsize);\ntypedef void (*TagCloseFunct) (DilloHtml *html);\n\ntypedef enum {\n   SEEK_ATTR_START,\n   MATCH_ATTR_NAME,\n   SEEK_TOKEN_START,\n   SEEK_VALUE_START,\n   SKIP_VALUE,\n   GET_VALUE,\n   FINISHED\n} DilloHtmlTagParsingState;\n\ntypedef enum {\n   HTML_LeftTrim      = 1 << 0,\n   HTML_RightTrim     = 1 << 1,\n   HTML_ParseEntities = 1 << 2\n} DilloHtmlTagParsingFlags;\n\n\n/*\n * Exported function with C linkage.\n */\nextern \"C\" {\nvoid *a_Html_text(const char *type, void *P, CA_Callback_t *Call,void **Data);\n}\n\n/*-----------------------------------------------------------------------------\n * Forward declarations\n *---------------------------------------------------------------------------*/\nstatic void Html_process_tag(DilloHtml *html, char *tag, int tagsize);\nstatic void Rss_process_tag(DilloHtml *html, char *tag, int tagsize);\nstatic int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof,\n                          void process_tag(DilloHtml *html, char *tag, int tagsize));\nstatic int Gemini_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof);\nstatic int Gopher_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof);\nstatic int Markdown_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof);\nstatic bool Html_load_image(BrowserWindow *bw, DilloUrl *url,\n                            const DilloUrl *requester, DilloImage *image);\nstatic void Html_callback(int Op, CacheClient_t *Client);\nstatic void Html_tag_cleanup_at_close(DilloHtml *html, int TagIdx);\nint a_Html_tag_index(const char *tag);\n\n/*-----------------------------------------------------------------------------\n * Local Data\n *---------------------------------------------------------------------------*/\n/* Parsing table structure */\ntypedef struct {\n   const char *name;      /* element name */\n   unsigned char Flags;   /* flags (explained near the table data) */\n   char EndTag;           /* Is it Required, Optional or Forbidden */\n   TagOpenFunct open;     /* Open function */\n   TagOpenFunct content;  /* Content function */\n   TagCloseFunct close;   /* Close function */\n} TagInfo;\n\n/* Some element indexes required in scattered places */\nstatic int\n   i_A = a_Html_tag_index(\"a\"),\n   i_BODY = a_Html_tag_index(\"body\"),\n   i_BUTTON = a_Html_tag_index(\"button\"),\n   i_DD = a_Html_tag_index(\"dd\"),\n   i_DT = a_Html_tag_index(\"dt\"),\n   i_HTML = a_Html_tag_index(\"html\"),\n   i_HR = a_Html_tag_index(\"hr\"),\n   i_LI = a_Html_tag_index(\"li\"),\n   i_OPTGROUP = a_Html_tag_index(\"optgroup\"),\n   i_OPTION = a_Html_tag_index(\"option\"),\n   i_P  = a_Html_tag_index(\"p\"),\n   i_SELECT = a_Html_tag_index(\"select\"),\n   i_TEXTAREA = a_Html_tag_index(\"textarea\"),\n   i_TD = a_Html_tag_index(\"td\"),\n   i_TR = a_Html_tag_index(\"tr\"),\n   i_TH = a_Html_tag_index(\"th\");\n\n\n/*-----------------------------------------------------------------------------\n *-----------------------------------------------------------------------------\n * Main Code\n *-----------------------------------------------------------------------------\n *---------------------------------------------------------------------------*/\n\n/*\n * Collect HTML error strings.\n */\nvoid DilloHtml::bugMessage(const char *format, ... )\n{\n   va_list argp;\n\n   if (bw->num_page_bugs)\n      dStr_append_c(bw->page_bugs, '\\n');\n   dStr_sprintfa(bw->page_bugs,\n                 \"HTML warning: line %d, \",\n                 getCurrLineNumber());\n   va_start(argp, format);\n   dStr_vsprintfa(bw->page_bugs, format, argp);\n   va_end(argp);\n   a_UIcmd_set_bug_prog(bw, ++bw->num_page_bugs);\n}\n\n/*\n * Wrapper for a_Url_new that adds an error detection message.\n * If use_base_url is TRUE, it uses base_url. Otherwise it uses html->base_url.\n */\nDilloUrl *a_Html_url_new(DilloHtml *html,\n                         const char *url_str, const char *base_url,\n                         int use_base_url)\n{\n   DilloUrl *url;\n   int n_ic, n_ic_spc;\n\n   url = a_Url_new(url_str,\n                   (use_base_url) ? base_url : URL_STR_(html->base_url));\n   if ((n_ic = URL_ILLEGAL_CHARS(url)) != 0) {\n      const char *suffix = (n_ic) > 1 ? \"s\" : \"\";\n      n_ic_spc = URL_ILLEGAL_CHARS_SPC(url);\n      if (n_ic == n_ic_spc) {\n         BUG_MSG(\"URL has %d illegal space%s ('%s').\", n_ic, suffix, url_str);\n      } else if (n_ic_spc == 0) {\n         BUG_MSG(\"URL has %d illegal byte%s in {00-1F, 7F-FF} range ('%s').\",\n                 n_ic, suffix, url_str);\n      } else {\n         BUG_MSG(\"URL has %d illegal byte%s: \"\n                 \"%d space%s and %d in {00-1F, 7F-FF} range ('%s').\",\n                 n_ic, suffix,\n                 n_ic_spc, n_ic_spc > 1 ? \"s\" : \"\", n_ic-n_ic_spc, url_str);\n      }\n   }\n   return url;\n}\n\n/*\n * Set callback function and callback data for the\n * \"text/html\", \"text/gemini\", \"text/gopher\" or \"text/markdown\" MIME type.\n */\nvoid *a_Html_text(const char *Type, void *P, CA_Callback_t *Call, void **Data)\n{\n   DilloWeb *web = (DilloWeb*)P;\n   DilloHtml *html = new DilloHtml(web->bw, web->url, Type);\n\n   *Data = (void*)html;\n   *Call = (CA_Callback_t)Html_callback;\n\n   return (void*)html->dw;\n}\n\nstatic void Html_free(void *data)\n{\n   delete ((DilloHtml*)data);\n}\n\n/*\n * Used by the \"Load images\" page menuitem.\n */\nvoid a_Html_load_images(void *v_html, DilloUrl *pattern)\n{\n   DilloHtml *html = (DilloHtml*)v_html;\n\n   html->loadImages(pattern);\n}\n\n/*\n * Search for form\n */\nstatic bool Html_contains_form(DilloHtml *html, void *v_form)\n{\n   for (int i = 0; i < html->forms->size(); i++) {\n      if (html->forms->get(i) == v_form) {\n         return true;\n      }\n   }\n   return false;\n}\n\n/*\n * Used by the \"Submit form\" form menuitem.\n */\nvoid a_Html_form_submit(void *v_html, void *v_form)\n{\n   DilloHtml *html = (DilloHtml*)v_html;\n\n   if (Html_contains_form(html, v_form)) {\n      /* it's still valid */\n     a_Html_form_submit2(v_form);\n   }\n}\n\n/*\n * Used by the \"Reset form\" form menuitem.\n */\nvoid a_Html_form_reset(void *v_html, void *v_form)\n{\n   DilloHtml *html = (DilloHtml*)v_html;\n\n   if (Html_contains_form(html, v_form)) {\n      /* it's still valid */\n     a_Html_form_reset2(v_form);\n   }\n}\n\n/*\n * Used by the \"Show/Hide hiddens\" form menuitem.\n */\nvoid a_Html_form_display_hiddens(void *v_html, void *v_form, bool_t display)\n{\n   DilloHtml *html = (DilloHtml*)v_html;\n\n   if (Html_contains_form(html, v_form)) {\n      /* it's still valid */\n      a_Html_form_display_hiddens2(v_form, (display != 0));\n   }\n}\n\n/*\n * Set the URL data for image maps.\n */\nstatic void Html_set_link_coordinates(DilloHtml *html, int link, int x, int y)\n{\n   char data[64];\n\n   if (x != -1) {\n      snprintf(data, 64, \"?%d,%d\", x, y);\n      a_Url_set_ismap_coords(html->links->get(link), data);\n   }\n}\n\n/*\n * Create a new link, set it as the url's parent\n * and return the index.\n */\nstatic int Html_set_new_link(DilloHtml *html, DilloUrl **url)\n{\n   int nl = html->links->size();\n   html->links->increase();\n   html->links->set(nl, (*url) ? *url : NULL);\n   return nl;\n}\n\n/*\n * Evaluates the ALIGN attribute (left|center|right|justify) and\n * sets the style at the top of the stack.\n */\nvoid a_Html_tag_set_align_attr(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *align;\n\n   if ((align = a_Html_get_attr(html, tag, tagsize, \"align\"))) {\n      TextAlignType textAlignType = TEXT_ALIGN_LEFT;\n\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"The align attribute is obsolete in HTML5.\");\n\n      if (dStrAsciiCasecmp (align, \"left\") == 0)\n         textAlignType = TEXT_ALIGN_LEFT;\n      else if (dStrAsciiCasecmp (align, \"right\") == 0)\n         textAlignType = TEXT_ALIGN_RIGHT;\n      else if (dStrAsciiCasecmp (align, \"center\") == 0)\n         textAlignType = TEXT_ALIGN_CENTER;\n      else if (dStrAsciiCasecmp (align, \"justify\") == 0)\n         textAlignType = TEXT_ALIGN_JUSTIFY;\n#if 0\n      else if (dStrAsciiCasecmp (align, \"char\") == 0) {\n         /* TODO: Actually not supported for <p> etc. */\n         v.textAlign = TEXT_ALIGN_STRING;\n         if ((charattr = a_Html_get_attr(html, tag, tagsize, \"char\"))) {\n            if (charattr[0] == 0)\n               /* TODO: ALIGN=\" \", and even ALIGN=\"&32;\" will reult in\n                * an empty string (don't know whether the latter is\n                * correct, has to be clarified with the specs), so\n                * that for empty strings, \" \" is assumed. */\n               style_attrs.textAlignChar = ' ';\n            else\n               style_attrs.textAlignChar = charattr[0];\n         } else\n            /* TODO: Examine LANG attr of <html>. */\n            style_attrs.textAlignChar = '.';\n      }\n#endif\n      html->styleEngine->setNonCssHint(CSS_PROPERTY_TEXT_ALIGN, CSS_TYPE_ENUM,\n                                       textAlignType);\n   }\n}\n\n/*\n * Evaluates the VALIGN attribute (top|bottom|middle|baseline) and\n * sets the style in style_attrs. Returns true when set.\n */\nbool a_Html_tag_set_valign_attr(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attr;\n   VAlignType valign;\n\n   if ((attr = a_Html_get_attr(html, tag, tagsize, \"valign\"))) {\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"The valign attribute is obsolete in HTML5.\");\n\n      if (dStrAsciiCasecmp (attr, \"top\") == 0)\n         valign = VALIGN_TOP;\n      else if (dStrAsciiCasecmp (attr, \"bottom\") == 0)\n         valign = VALIGN_BOTTOM;\n      else if (dStrAsciiCasecmp (attr, \"baseline\") == 0)\n         valign = VALIGN_BASELINE;\n      else\n         valign = VALIGN_MIDDLE;\n\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_VERTICAL_ALIGN,\n                                        CSS_TYPE_ENUM, valign);\n      return true;\n   } else\n      return false;\n}\n\n\n/*\n * Create and add a new Textblock to the current Textblock. Typically\n * only one of addBreaks and addBreakOpt is true.\n */\nstatic void Html_add_textblock(DilloHtml *html, bool addBreaks, int breakSpace,\n                               bool addBreakOpt)\n{\n   Textblock *textblock = new Textblock (prefs.limit_text_width);\n   Style *style;\n\n   if (addBreaks) {\n      StyleAttrs attrs = *(html->style ());\n      attrs.display = DISPLAY_BLOCK;\n      style = Style::create (&attrs);\n   } else {\n      style = html->style ();\n      style->ref ();\n   }\n\n   if (addBreaks)\n      HT2TB(html)->addParbreak (breakSpace, html->wordStyle ());\n\n   HT2TB(html)->addWidget (textblock, style); /* Works also for floats etc. */\n   if (addBreakOpt)\n      HT2TB(html)->addBreakOption (html->style (), false);\n\n   if (addBreaks)\n      HT2TB(html)->addParbreak (breakSpace, html->wordStyle ());\n   S_TOP(html)->textblock = html->dw = textblock;\n   if (addBreaks)\n      S_TOP(html)->hand_over_break = true;\n\n   style->unref ();\n}\n\nstatic bool Html_must_add_breaks(DilloHtml *html)\n{\n   return HT2TB(html)->mustAddBreaks (html->style ());\n}\n\n/*\n * Create and initialize a new DilloHtml class\n */\nDilloHtml::DilloHtml(BrowserWindow *p_bw, const DilloUrl *url,\n                     const char *content_type)\n{\n   /* Init main variables */\n   bw = p_bw;\n   page_url = a_Url_dup(url);\n   base_url = a_Url_dup(url);\n   dw = NULL;\n\n   /* Init event receiver */\n   linkReceiver.html = this;\n   HT2LT(this)->connectLink (&linkReceiver);\n\n   a_Bw_add_doc(p_bw, this);\n\n   /* Init for-parsing variables */\n   Start_Buf = NULL;\n   Start_Ofs = 0;\n\n   _MSG(\"DilloHtml(): content type: %s\\n\", content_type);\n   this->content_type = dStrdup(content_type);\n\n   /* get charset */\n   a_Misc_parse_content_type(content_type, NULL, NULL, &charset);\n\n   stop_parser = false;\n\n   CurrOfs = OldOfs = 0;\n   OldLine = 1;\n\n   DocType = DT_NONE;    /* assume Tag Soup 0.0!   :-) */\n   DocTypeVersion = 0.0f;\n\n   styleEngine = new StyleEngine (HT2LT (this), page_url, base_url);\n\n   cssUrls = new misc::SimpleVector <DilloUrl*> (1);\n\n   stack = new misc::SimpleVector <DilloHtmlState> (16);\n   stack->increase();\n   stack->getRef(0)->parse_mode = DILLO_HTML_PARSE_MODE_INIT;\n   stack->getRef(0)->table_mode = DILLO_HTML_TABLE_MODE_NONE;\n   stack->getRef(0)->table_border_mode = DILLO_HTML_TABLE_BORDER_SEPARATE;\n   stack->getRef(0)->cell_text_align_set = false;\n   stack->getRef(0)->display_none = false;\n   stack->getRef(0)->list_type = HTML_LIST_NONE;\n   stack->getRef(0)->list_number = 0;\n   stack->getRef(0)->tag_idx = -1;               /* MUST not be used */\n   stack->getRef(0)->textblock = NULL;\n   stack->getRef(0)->table = NULL;\n   stack->getRef(0)->ref_list_item = NULL;\n   stack->getRef(0)->hand_over_break = false;\n\n   InFlags = IN_NONE;\n\n   Stash = dStr_new(\"\");\n   StashSpace = false;\n\n   pre_column = 0;\n   PreFirstChar = false;\n   PrevWasCR = false;\n   InVisitedLink = false;\n   ReqTagClose = false;\n   TagSoup = true;\n   loadCssFromStash = false;\n   PrevWasBodyClose = false;\n   PrevWasHtmlClose = false;\n\n   Num_HTML = Num_HEAD = Num_BODY = Num_TITLE = 0;\n\n   attr_data = dStr_sized_new(1024);\n\n   non_css_link_color = -1;\n   non_css_visited_color = -1;\n   visited_color = -1;\n\n   /* Init page-handling variables */\n   forms = new misc::SimpleVector <DilloHtmlForm*> (1);\n   inputs_outside_form = new misc::SimpleVector <DilloHtmlInput*> (1);\n   links = new misc::SimpleVector <DilloUrl*> (64);\n   images = new misc::SimpleVector <DilloHtmlImage*> (16);\n\n   /* Init Gemini, Gopher and Markdown parsers stuff */\n   in_pre = false;\n   in_link = false;\n   list_level = -1;\n   last_partial_line[0] = '\\0';\n\n   /* Initialize the main widget */\n   initDw();\n   /* Hook destructor to the dw delete call */\n   dw->setDeleteCallback(Html_free, this);\n}\n\n/*\n * Miscellaneous initializations for Dw\n */\nvoid DilloHtml::initDw()\n{\n   dReturn_if_fail (dw == NULL);\n\n   /* Create the main widget */\n   dw = stack->getRef(0)->textblock =  new Textblock (prefs.limit_text_width);\n\n   bw->num_page_bugs = 0;\n   dStr_truncate(bw->page_bugs, 0);\n}\n\n/*\n * Free memory used by the DilloHtml class.\n */\nDilloHtml::~DilloHtml()\n{\n   _MSG(\"::~DilloHtml(this=%p)\\n\", this);\n\n   freeParseData();\n\n   a_Bw_remove_doc(bw, this);\n\n   a_Url_free(page_url);\n   a_Url_free(base_url);\n\n   for (int i = 0; i < cssUrls->size(); i++)\n      a_Url_free(cssUrls->get(i));\n   delete (cssUrls);\n\n   for (int i = 0; i < forms->size(); i++)\n      a_Html_form_delete (forms->get(i));\n   delete(forms);\n\n   for (int i = 0; i < inputs_outside_form->size(); i++)\n      a_Html_input_delete(inputs_outside_form->get(i));\n   delete(inputs_outside_form);\n\n   for (int i = 0; i < links->size(); i++)\n      a_Url_free(links->get(i));\n   delete (links);\n\n   for (int i = 0; i < images->size(); i++) {\n      DilloHtmlImage *img = images->get(i);\n      a_Url_free(img->url);\n      a_Image_unref(img->image);\n      dFree(img);\n   }\n   delete (images);\n\n   delete styleEngine;\n}\n\n/*\n * Process the newly arrived html and put it into the page structure.\n * (This function is called by Html_callback whenever there's new data)\n */\nvoid DilloHtml::write(char *Buf, int BufSize, int Eof)\n{\n   int token_start;\n   char *buf = Buf + Start_Ofs;\n   int bufsize = BufSize - Start_Ofs;\n\n   _MSG(\"DilloHtml::write BufSize=%d Start_Ofs=%d\\n\", BufSize, Start_Ofs);\n#if 0\n   char *aux = dStrndup(Buf, BufSize);\n   MSG(\" {%s}\\n\", aux);\n   dFree(aux);\n#endif\n\n   /* Update Start_Buf. It may be used after the parser is stopped */\n   Start_Buf = Buf;\n\n   dReturn_if (dw == NULL);\n   dReturn_if (stop_parser == true);\n\n   if(!strncmp(this->content_type, \"text/gemini\", 11)) {\n      token_start = Gemini_write_raw(this, buf, bufsize, Eof);\n   } else if(!strncmp(this->content_type, \"text/gopher\", 11)) {\n      token_start = Gopher_write_raw(this, buf, bufsize, Eof);\n   } else if(!strncmp(this->content_type, \"text/markdown\", 13)) {\n      token_start = Markdown_write_raw(this, buf, bufsize, Eof);\n   } else if(strstr(this->content_type, \"/rss\") ||\n             (bufsize > 5 && !strncmp(buf, \"<?xml\", 5) &&\n              strstr(buf, \"<rss version=\"))) {\n      if(!strstr(this->content_type, \"/rss\")) {\n         // change content type to an appropriate one\n         dFree(content_type);\n         this->content_type = dStrdup(\"text/rss\");\n      }\n      token_start = Html_write_raw(this, buf, bufsize, Eof, &Rss_process_tag);\n   } else {\n      token_start = Html_write_raw(this, buf, bufsize, Eof, &Html_process_tag);\n   }\n   Start_Ofs += token_start;\n}\n\n/*\n * Return the line number of the tag/word being processed by the parser.\n * Also update the offsets.\n */\nint DilloHtml::getCurrLineNumber()\n{\n   int i, ofs, line;\n   const char *p = Start_Buf;\n\n   dReturn_val_if_fail(p != NULL, -1);\n   /* Disable line counting for META hack. Buffers differ. */\n   dReturn_val_if((InFlags & IN_META_HACK), -1);\n\n   ofs = CurrOfs;\n   line = OldLine;\n   for (i = OldOfs; i < ofs; ++i)\n      if (p[i] == '\\n' || (p[i] == '\\r' && p[i+1] != '\\n'))\n         ++line;\n   OldOfs = CurrOfs;\n   OldLine = line;\n   return line;\n}\n\n/*\n * Free parsing data.\n */\nvoid DilloHtml::freeParseData()\n{\n   delete(stack);\n\n   dStr_free(Stash, TRUE);\n   dStr_free(attr_data, TRUE);\n   dFree(content_type);\n   dFree(charset);\n}\n\n/*\n * Finish parsing a HTML page. Close the parser and close the client.\n * The class is not deleted here, it remains until the widget is destroyed.\n */\nvoid DilloHtml::finishParsing(int ClientKey)\n{\n   int si;\n\n   dReturn_if (stop_parser == true);\n\n   /* flag we've already parsed up to the last byte */\n   InFlags |= IN_EOF;\n\n   /* force the close of elements left open (TODO: not for XHTML) */\n   while ((si = stack->size() - 1)) {\n      if (stack->getRef(si)->tag_idx != -1) {\n         Html_tag_cleanup_at_close(this, stack->getRef(si)->tag_idx);\n      }\n   }\n\n   /* Nothing left to do with the parser. Clear all flags, except EOF. */\n   InFlags = IN_EOF;\n\n   /* Remove this client from our active list */\n   a_Bw_close_client(bw, ClientKey);\n}\n\n/*\n * Allocate and insert form information.\n */\nint DilloHtml::formNew(DilloHtmlMethod method, const DilloUrl *action,\n                       DilloHtmlEnc enc, const char *charset)\n{\n   // avoid data loss on repush after CSS stylesheets have been loaded\n   bool enabled = bw->NumPendingStyleSheets == 0;\n   DilloHtmlForm *form = a_Html_form_new (this, method, action,\n                                          enc, charset, enabled);\n   int nf = forms->size ();\n   forms->increase ();\n   forms->set (nf, form);\n   _MSG(\"Html formNew: action=%s nform=%d\\n\", action, nf);\n   return forms->size();\n}\n\n/*\n * Get the current form.\n */\nDilloHtmlForm *DilloHtml::getCurrentForm ()\n{\n   return forms->get (forms->size() - 1);\n}\n\nbool_t DilloHtml::unloadedImages()\n{\n   for (int i = 0; i < images->size(); i++) {\n      if (images->get(i)->image != NULL) {\n         return TRUE;\n      }\n   }\n   return FALSE;\n}\n\n/*\n * Load images if they were disabled.\n */\nvoid DilloHtml::loadImages (const DilloUrl *pattern)\n{\n   dReturn_if (a_Bw_expecting(bw));\n\n   /* If the user asked for a specific image, the user (NULL) is the requester,\n    * and the domain mechanism will always permit the request. But if the user\n    * just asked for all images (clicking \"Load images\"), use the page URL as\n    * the requester so that the domain mechanism can act as a filter.\n    * If the possible patterns become more complex, it might be good to have\n    * the caller supply the requester instead.\n    */\n   const DilloUrl *requester = pattern ? NULL : this->page_url;\n\n   for (int i = 0; i < images->size(); i++) {\n      DilloHtmlImage *hi = images->get(i);\n\n      if (hi->image) {\n         assert(hi->url);\n         if ((!pattern) || (!a_Url_cmp(hi->url, pattern))) {\n            if (Html_load_image(bw, hi->url, requester, hi->image)) {\n               a_Image_unref (hi->image);\n               hi->image = NULL;  // web owns it now\n            }\n         }\n      }\n   }\n}\n\n/*\n * Save URL in a vector (may be loaded later).\n */\nvoid DilloHtml::addCssUrl(const DilloUrl *url)\n{\n   int nu = cssUrls->size();\n   cssUrls->increase();\n   cssUrls->set(nu, a_Url_dup(url));\n}\n\nbool DilloHtml::HtmlLinkReceiver::enter (Widget *widget, int link, int img,\n                                         int x, int y)\n{\n   BrowserWindow *bw = html->bw;\n\n   _MSG(\" ** \");\n   if (link == -1) {\n      _MSG(\" Link  LEAVE  notify...\\n\");\n      a_UIcmd_set_msg(bw, \"\");\n   } else {\n      _MSG(\" Link  ENTER  notify...\\n\");\n      Html_set_link_coordinates(html, link, x, y);\n      a_UIcmd_set_msg(bw, \"%s\", URL_STR(html->links->get(link)));\n   }\n   return true;\n}\n\n/*\n * Handle the \"press\" signal.\n */\nbool DilloHtml::HtmlLinkReceiver::press (Widget *widget, int link, int img,\n                                         int x, int y, EventButton *event)\n{\n   BrowserWindow *bw = html->bw;\n   int ret = false;\n   DilloUrl *linkurl = NULL;\n\n   _MSG(\"pressed button %d\\n\", event->button);\n   if (event->button == 3) {\n      // popup menus\n      if (img != -1) {\n         // image menu\n         if (link != -1)\n            linkurl = html->links->get(link);\n         const bool_t loaded_img = (html->images->get(img)->image == NULL);\n         a_UIcmd_image_popup(bw, html->images->get(img)->url, loaded_img,\n                             html->page_url, linkurl);\n         ret = true;\n      } else {\n         if (link == -1) {\n            a_UIcmd_page_popup(bw, bw->num_page_bugs != 0, html->cssUrls);\n            ret = true;\n         } else {\n            a_UIcmd_link_popup(bw, html->links->get(link));\n            ret = true;\n         }\n      }\n   }\n   return ret;\n}\n\n/*\n * Handle the \"click\" signal.\n */\nbool DilloHtml::HtmlLinkReceiver::click (Widget *widget, int link, int img,\n                                         int x, int y, EventButton *event)\n{\n   BrowserWindow *bw = html->bw;\n\n   if ((img != -1) && (html->images->get(img)->image)) {\n      // clicked an image that has not already been loaded\n      if (event->button == 1){\n         // load all instances of this image\n         DilloUrl *pattern = html->images->get(img)->url;\n         html->loadImages(pattern);\n         return true;\n      }\n   }\n\n   if (link != -1) {\n      DilloUrl *url = html->links->get(link);\n      _MSG(\"clicked on URL %d: %s\\n\", link, a_Url_str (url));\n\n      Html_set_link_coordinates(html, link, x, y);\n\n      if (event->button == 1) {\n         a_UIcmd_open_url(bw, url);\n      } else if (event->button == 2) {\n         if (prefs.middle_click_opens_new_tab) {\n            int focus = prefs.focus_new_tab ? 1 : 0;\n            if (event->state == SHIFT_MASK) focus = !focus;\n            a_UIcmd_open_url_nt(bw, url, focus);\n         } else\n            a_UIcmd_open_url_nw(bw, url);\n      } else {\n         return false;\n      }\n\n      /* Change the link color to \"visited\" as visual feedback */\n      for (Widget *w = widget; w; w = w->getParent()) {\n         _MSG(\"  ->%s\\n\", w->getClassName());\n         if (w->instanceOf(dw::Textblock::CLASS_ID)) {\n            ((Textblock*)w)->changeLinkColor (link, html->visited_color);\n            break;\n         }\n      }\n   }\n   return true;\n}\n\n/*\n * Initialize the stash buffer\n */\nvoid a_Html_stash_init(DilloHtml *html)\n{\n   S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_STASH;\n   html->StashSpace = false;\n   dStr_truncate(html->Stash, 0);\n}\n\n/*\n * This is M$ non-standard \"smart quotes\" (w1252). Now even deprecated by them!\n *\n * SGML for HTML4.01 defines c >= 128 and c <= 159 as UNUSED.\n * TODO: Probably I should remove this hack. --Jcid\n */\nstatic int Html_ms_stupid_quotes_2ucs(int codepoint)\n{\n   int ret;\n   switch (codepoint) {\n   case 145:\n   case 146: ret = '\\''; break;\n   case 147:\n   case 148: ret = '\"'; break;\n   case 149: ret = 176; break;\n   case 150:\n   case 151: ret = '-'; break;\n   default:  ret = codepoint; break;\n   }\n   return ret;\n}\n\n/*\n * Parse a numeric character reference (e.g., \"&#47;\" or \"&#x2F;\").\n * The \"&#\" has already been consumed.\n */\nstatic const char *Html_parse_numeric_charref(DilloHtml *html, char *tok,\n                                              bool_t is_attr, int *entsize)\n{\n   static char buf[5];\n   char *s = tok;\n   int n, codepoint = -1;\n\n   errno = 0;\n\n   if (*s == 'x' || *s == 'X') {\n      if (isxdigit(*++s)) {\n         /* strtol with base 16 accepts leading \"0x\" - we don't */\n         if (*s == '0' && s[1] == 'x') {\n            s++;\n            codepoint = 0;\n         } else {\n            codepoint = strtol(s, &s, 16);\n         }\n      }\n   } else if (isdigit(*s)) {\n      codepoint = strtol(s, &s, 10);\n   }\n   if (errno)\n      codepoint = -1;\n\n   if (*s == ';')\n      s++;\n   else {\n      if (prefs.show_extra_warnings && (html->DocType == DT_XHTML ||\n          (html->DocType == DT_HTML && html->DocTypeVersion <= 4.01f))) {\n         char c = *s;\n         *s = '\\0';\n         BUG_MSG(\"Character reference '&#%s' lacks ';'.\", tok);\n         *s = c;\n      }\n      /* Don't require ';' for old HTML, except that our current heuristic\n       * is to require it in attributes to avoid cases like \"&copy=1\" found\n       * in URLs.\n       */\n      if (is_attr || html->DocType == DT_XHTML ||\n          (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) {\n         return NULL;\n      }\n\n   }\n   if ((codepoint < 0x20 && codepoint != '\\t' && codepoint != '\\n' &&\n        codepoint != '\\f') ||\n       (codepoint >= 0x7f && codepoint <= 0x9f) ||\n       (codepoint >= 0xd800 && codepoint <= 0xdfff) || codepoint > 0x10ffff ||\n       ((codepoint & 0xfffe) == 0xfffe) ||\n       (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) &&\n        codepoint > 0xffff)) {\n      /* this catches null bytes, errors, codes out of range, disallowed\n       * control chars, permanently undefined chars, and surrogates.\n       */\n      char c = *s;\n      *s = '\\0';\n      BUG_MSG(\"Numeric character reference '&#%s' is not valid.\", tok);\n      *s = c;\n\n      codepoint = (codepoint >= 145 && codepoint <= 151) ?\n                  Html_ms_stupid_quotes_2ucs(codepoint) : -1;\n   }\n   if (codepoint != -1) {\n      if (codepoint >= 128) {\n         n = a_Utf8_encode(codepoint, buf);\n      } else {\n         n = 1;\n         buf[0] = (char) codepoint;\n      }\n      assert(n < 5);\n      buf[n] = '\\0';\n      *entsize = s-tok+2;\n      return buf;\n   } else {\n      return NULL;\n   }\n}\n\n/*\n * Comparison function for binary search\n */\nstatic int Html_charref_comp(const void *a, const void *b)\n{\n   return strcmp(((Charref_t *)a)->ref, ((Charref_t *)b)->ref);\n}\n\n/*\n * Binary search of 'key' in charref list\n */\nstatic Charref_t *Html_charref_search(char *key)\n{\n   Charref_t RefKey;\n\n   RefKey.ref = key;\n   return (Charref_t*) bsearch(&RefKey, Charrefs, NumRef,\n                       sizeof(Charref_t), Html_charref_comp);\n}\n\n/*\n * Parse a named character reference (e.g., \"&amp;\" or \"&hellip;\").\n * The \"&\" has already been consumed.\n */\nstatic const char *Html_parse_named_charref(DilloHtml *html, char *tok,\n                                            bool_t is_attr, int *entsize)\n{\n   Charref_t *p;\n   char c;\n   char *s = tok;\n   const char *ret = NULL;\n\n   while (*++s && (isalnum(*s) || strchr(\":_.-\", *s))) ;\n   c = *s;\n   *s = '\\0';\n   if (c != ';') {\n      if (prefs.show_extra_warnings && (html->DocType == DT_XHTML ||\n          (html->DocType == DT_HTML && html->DocTypeVersion <= 4.01f)))\n         BUG_MSG(\"Character reference '&%s' lacks ';'.\", tok);\n\n      /* Don't require ';' for old HTML, except that our current heuristic\n       * is to require it in attributes to avoid cases like \"&copy=1\" found\n       * in URLs.\n       */\n      if (is_attr || html->DocType == DT_XHTML ||\n          (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) {\n         return ret;\n      }\n   }\n\n   if ((p = Html_charref_search(tok))) {\n      ret = (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) ?\n            p->html5_str : p->html4_str;\n   }\n\n   if (!ret && html->DocType == DT_XHTML && !strcmp(tok, \"apos\"))\n      ret = \"'\";\n\n   *s = c;\n   if (c == ';')\n      s++;\n\n   if (!ret) {\n      c = *s;\n      *s = '\\0';\n      BUG_MSG(\"Undefined character reference '&%s'.\", tok);\n      *s = c;\n   }\n   *entsize = s-tok+1;\n   return ret;\n}\n\n/*\n * Given an entity, return the corresponding string.\n * Returns NULL if not a valid entity.\n *\n * The first character *token is assumed to be == '&'\n *\n * For valid entities, *entsize is set to the length of the parsed entity.\n */\nstatic const char *Html_parse_entity(DilloHtml *html, const char *token,\n                                     int toksize, int *entsize, bool_t is_attr)\n{\n   const char *ret = NULL;\n   char *tok;\n\n   if (toksize > 50) {\n      /* In pathological cases, attributes can be megabytes long and filled\n       * with character references. As of HTML5, the longest defined character\n       * reference is about 32 bytes long.\n       */\n      toksize = 50;\n   }\n\n   token++;\n   tok = dStrndup(token, (uint_t)toksize);\n\n   if (*tok == '#') {\n      ret = Html_parse_numeric_charref(html, tok+1, is_attr, entsize);\n   } else if (isalpha(*tok)) {\n      ret = Html_parse_named_charref(html, tok, is_attr, entsize);\n   } else if (prefs.show_extra_warnings &&\n       (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f))) {\n      // HTML5 doesn't mind literal '&'s.\n      BUG_MSG(\"Literal '&'.\");\n   }\n   dFree(tok);\n\n   return ret;\n}\n\n/*\n * Parse all the entities in a token. Takes the token and its length, and\n * returns a newly allocated string.\n */\nchar *a_Html_parse_entities(DilloHtml *html, const char *token, int toksize)\n{\n   const char *esc_set = \"&\";\n   int i, s, entsize;\n   char *str;\n\n   s = strcspn(token, esc_set);\n   if (s >= toksize) {\n      /* no ampersands */\n      str = dStrndup(token, toksize);\n   } else {\n      Dstr *ds = dStr_sized_new(toksize);\n\n      dStr_append_l(ds, token, s);\n\n      for (i = s; i < toksize; i++) {\n         const char *entstr;\n         const bool_t is_attr = FALSE;\n\n         if (token[i] == '&' &&\n             (entstr = Html_parse_entity(html, token+i, toksize-i, &entsize,\n                                         is_attr))) {\n            dStr_append(ds, entstr);\n            i += entsize-1;\n         } else {\n            dStr_append_c(ds, token[i]);\n         }\n      }\n      str = ds->str;\n      dStr_free(ds, 0);\n   }\n   return str;\n}\n\n/*\n * For white-space: pre-line, we must break the line if encountering a newline.\n * Otherwise, collapse whitespace as usual.\n */\nstatic void Html_process_space_pre_line(DilloHtml *html, const char *space,\n                                        int spacesize)\n{\n   int i, breakCnt = 0;\n\n   for (i = 0; i < spacesize; i++) {\n      /* Support for \"\\r\", \"\\n\" and \"\\r\\n\" line breaks */\n      if (space[i] == '\\r' || (space[i] == '\\n' && !html->PrevWasCR)) {\n         breakCnt++;\n         html->PrevWasCR = (space[i] == '\\r');\n\n         HT2TB(html)->addLinebreak (html->wordStyle ());\n      }\n   }\n   if (breakCnt == 0) {\n      HT2TB(html)->addSpace(html->wordStyle ());\n   }\n}\n\n/*\n * Parse spaces\n */\nstatic void Html_process_space(DilloHtml *html, const char *space,\n                               int spacesize)\n{\n   char *spc;\n   int i, offset;\n   DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode;\n\n   if (S_TOP(html)->display_none) {\n      /* do nothing */\n   } else if (parse_mode == DILLO_HTML_PARSE_MODE_STASH) {\n      html->StashSpace = (html->Stash->len > 0);\n\n   } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {\n      dStr_append_l(html->Stash, space, spacesize);\n\n   } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) {\n      int spaceCnt = 0;\n\n      /* re-scan the string for characters that cause line breaks */\n      for (i = 0; i < spacesize; i++) {\n         /* Support for \"\\r\", \"\\n\" and \"\\r\\n\" line breaks (skips the first) */\n         if (!html->PreFirstChar &&\n             (space[i] == '\\r' || (space[i] == '\\n' && !html->PrevWasCR))) {\n\n            if (spaceCnt) {\n               spc = dStrnfill(spaceCnt, ' ');\n               HT2TB(html)->addText (spc, spaceCnt, html->wordStyle ());\n               dFree(spc);\n               spaceCnt = 0;\n            }\n            HT2TB(html)->addLinebreak (html->wordStyle ());\n            html->pre_column = 0;\n         }\n         html->PreFirstChar = false;\n\n         /* cr and lf should not be rendered -- they appear as a break */\n         switch (space[i]) {\n         case '\\r':\n         case '\\n':\n            break;\n         case '\\t':\n            if (prefs.show_extra_warnings)\n               BUG_MSG(\"TAB character inside <pre>.\");\n            offset = TAB_SIZE - html->pre_column % TAB_SIZE;\n            spaceCnt += offset;\n            html->pre_column += offset;\n            break;\n         default:\n            spaceCnt++;\n            html->pre_column++;\n            break;\n         }\n\n         html->PrevWasCR = (space[i] == '\\r');\n      }\n\n      if (spaceCnt) {\n         // add break possibility for the white-space:pre-wrap case\n         HT2TB(html)->addBreakOption (html->wordStyle (), false);\n         spc = dStrnfill(spaceCnt, ' ');\n         HT2TB(html)->addText (spc, spaceCnt, html->wordStyle ());\n         dFree(spc);\n      }\n\n   } else {\n      if (SGML_SPCDEL) {\n         /* SGML_SPCDEL ignores white space immediately after an open tag */\n      } else if (html->wordStyle ()->whiteSpace == WHITE_SPACE_PRE_LINE) {\n         Html_process_space_pre_line(html, space, spacesize);\n      } else {\n         HT2TB(html)->addSpace(html->wordStyle ());\n      }\n\n      if (parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY)\n         html->StashSpace = (html->Stash->len > 0);\n   }\n}\n\n/*\n * Handles putting the word into its proper place\n *  > STASH and VERBATIM --> html->Stash\n *  > otherwise it goes through addText()\n *\n * Entities are parsed (or not) according to parse_mode.\n * 'word' is a '\\0'-terminated string.\n */\nstatic void Html_process_word(DilloHtml *html, const char *word, int size)\n{\n   int i, j, start;\n   char *Pword;\n   DilloHtmlParseMode parse_mode = S_TOP(html)->parse_mode;\n\n   if (S_TOP(html)->display_none)\n      return;\n   if ((i = html->PrevWasHtmlClose ? 1 : html->PrevWasBodyClose ? 2 : 0)) {\n      BUG_MSG(\"Content after </%s> tag.\", i == 1 ? \"html\" : \"body\");\n      html->PrevWasHtmlClose = html->PrevWasBodyClose = false;\n   }\n\n   if (parse_mode == DILLO_HTML_PARSE_MODE_STASH ||\n       parse_mode == DILLO_HTML_PARSE_MODE_STASH_AND_BODY) {\n      if (html->StashSpace) {\n         dStr_append_c(html->Stash, ' ');\n         html->StashSpace = false;\n      }\n      Pword = a_Html_parse_entities(html, word, size);\n      dStr_append(html->Stash, Pword);\n      dFree(Pword);\n\n   } else if (parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {\n      /* word goes in untouched, it is not processed here. */\n      dStr_append_l(html->Stash, word, size);\n   }\n\n   if (parse_mode == DILLO_HTML_PARSE_MODE_STASH ||\n       parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {\n      /* skip until the closing instructions */\n\n   } else if (parse_mode == DILLO_HTML_PARSE_MODE_PRE) {\n      /* all this overhead is to catch white-space entities */\n      Pword = a_Html_parse_entities(html, word, size);\n      for (start = i = 0; Pword[i]; start = i)\n         if (isspace(Pword[i])) {\n            while (Pword[++i] && isspace(Pword[i])) ;\n            Html_process_space(html, Pword + start, i - start);\n         } else {\n            while (Pword[++i] && !isspace(Pword[i])) ;\n            HT2TB(html)->addText(Pword + start, i - start, html->wordStyle ());\n            html->pre_column += i - start;\n            html->PreFirstChar = false;\n         }\n      dFree(Pword);\n\n   } else {\n      const char *word2, *beyond_word2;\n\n      Pword = NULL;\n      if (!memchr(word,'&', size)) {\n         /* No entities */\n         word2 = word;\n         beyond_word2 = word + size;\n      } else {\n         /* Collapse white-space entities inside the word (except &nbsp;) */\n         Pword = a_Html_parse_entities(html, word, size);\n         /* Collapse adjacent \" \\t\\f\\n\\r\" characters into a single space */\n         for (i = j = 0; (Pword[i] = Pword[j]); ++i, ++j) {\n            if (strchr(\" \\t\\f\\n\\r\", Pword[i])) {\n               if (i == 0 || (i > 0 && Pword[i-1] != ' '))\n                  Pword[i] = ' ';\n               else\n                  for (--i; Pword[j+1] && strchr(\" \\t\\f\\n\\r\", Pword[j+1]); ++j)\n                     ;\n            }\n         }\n         word2 = Pword;\n         beyond_word2 = word2 + strlen(word2);\n      }\n      for (start = i = 0; word2[i]; start = i) {\n         int len;\n\n         if (isspace(word2[i])) {\n            while (word2[++i] && isspace(word2[i])) ;\n            Html_process_space(html, word2 + start, i - start);\n         } else if (!strncmp(word2+i, utf8_zero_width_space, 3)) {\n            i += 3;\n            HT2TB(html)->addBreakOption(html->wordStyle (), false);\n         } else if (a_Utf8_ideographic(word2+i, beyond_word2, &len)) {\n            i += len;\n            HT2TB(html)->addText(word2 + start, i - start, html->wordStyle ());\n            HT2TB(html)->addBreakOption(html->wordStyle (), false);\n         } else {\n            do {\n               i += len;\n            } while (word2[i] && !isspace(word2[i]) &&\n                     strncmp(word2+i, utf8_zero_width_space, 3) &&\n                     (!a_Utf8_ideographic(word2+i, beyond_word2, &len)));\n            HT2TB(html)->addText(word2 + start, i - start, html->wordStyle ());\n         }\n      }\n      if (Pword == word2)\n         dFree(Pword);\n   }\n}\n\n/*\n * Does the tag in tagstr (e.g. \"p\") match the tag in the tag, tagsize\n * structure, with the initial < skipped over (e.g. \"P align=center>\")?\n */\nstatic bool Html_match_tag(const char *tagstr, char *tag, int tagsize)\n{\n   int i;\n\n   for (i = 0; i < tagsize && tagstr[i] != '\\0'; i++) {\n      if (D_ASCII_TOLOWER(tagstr[i]) != D_ASCII_TOLOWER(tag[i]))\n         return false;\n   }\n   /* The test for '/' is for xml compatibility: \"empty/>\" will be matched. */\n   if (i < tagsize && (isspace(tag[i]) || tag[i] == '>' || tag[i] == '/'))\n      return true;\n   return false;\n}\n\n/*\n * This function is called after popping the stack, to\n * handle nested Textblock widgets.\n */\nstatic void Html_eventually_pop_dw(DilloHtml *html, bool hand_over_break)\n{\n   if (html->dw != S_TOP(html)->textblock) {\n      if (hand_over_break)\n         HT2TB(html)->handOverBreak (html->style ());\n      HT2TB(html)->flush ();\n      html->dw = S_TOP(html)->textblock;\n   }\n}\n\n/*\n * Push the tag (copying attributes from the top of the stack)\n */\nstatic void Html_push_tag(DilloHtml *html, int tag_idx)\n{\n   int n_items;\n\n   n_items = html->stack->size ();\n   html->stack->increase ();\n   /* We'll copy the former stack item and just change the tag and its index\n    * instead of copying all fields except for tag.  --Jcid */\n   *html->stack->getRef(n_items) = *html->stack->getRef(n_items - 1);\n   html->stack->getRef(n_items)->tag_idx = tag_idx;\n   html->dw = S_TOP(html)->textblock;\n}\n\n/*\n * Push the tag (used to force en element with optional open into the stack)\n * Note: now it's the same as Html_push_tag(), but things may change...\n */\nstatic void Html_force_push_tag(DilloHtml *html, int tag_idx)\n{\n   html->startElement (tag_idx);\n   Html_push_tag(html, tag_idx);\n}\n\n/*\n * Pop the top tag in the stack\n */\nstatic void Html_real_pop_tag(DilloHtml *html)\n{\n   bool hand_over_break;\n\n   html->styleEngine->endElement (S_TOP(html)->tag_idx);\n   hand_over_break = S_TOP(html)->hand_over_break;\n   html->stack->setSize (html->stack->size() - 1);\n   Html_eventually_pop_dw(html, hand_over_break);\n}\n\n\n\n/*\n * Some parsing routines.\n */\n\n/*\n * Used by a_Html_parse_length\n */\nstatic CssLength Html_parse_length_or_multi_length (const char *attr,\n                                                    char **endptr)\n{\n   CssLength l;\n   double v;\n   char *end;\n\n   v = strtod (attr, &end);\n   switch (*end) {\n   case '%':\n      end++;\n      l = CSS_CREATE_LENGTH (v / 100, CSS_LENGTH_TYPE_PERCENTAGE);\n      break;\n\n   case '*':\n      end++;\n      l = CSS_CREATE_LENGTH (v, CSS_LENGTH_TYPE_RELATIVE);\n      break;\n/*\n   The \"px\" suffix seems not allowed by HTML4.01 SPEC.\n   case 'p':\n      if (end[1] == 'x')\n         end += 2;\n*/\n   default:\n      l = CSS_CREATE_LENGTH (v, CSS_LENGTH_TYPE_PX);\n      break;\n   }\n\n   if (endptr)\n      *endptr = end;\n   return l;\n}\n\n\n/*\n * Returns a length or a percentage, or UNDEF_LENGTH in case\n * of an error, or if attr is NULL.\n */\nCssLength a_Html_parse_length (DilloHtml *html, const char *attr)\n{\n   CssLength l;\n   char *end;\n\n   l = Html_parse_length_or_multi_length (attr, &end);\n   if (CSS_LENGTH_TYPE (l) == CSS_LENGTH_TYPE_RELATIVE)\n      /* not allowed as &Length; */\n      l = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO);\n   else {\n      /* allow only whitespaces */\n      if (*end && !isspace (*end)) {\n         BUG_MSG(\"Garbage after length: '%s'.\", attr);\n         l = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO);\n      }\n   }\n\n   _MSG(\"a_Html_parse_length: \\\"%s\\\" %d\\n\", attr, CSS_LENGTH_VALUE(l));\n   return l;\n}\n\n/*\n * Parse a color attribute.\n * Return value: parsed color, or default_color (+ error msg) on error.\n */\nint32_t a_Html_color_parse(DilloHtml *html, const char *str,\n                           int32_t default_color)\n{\n   int err = 1;\n   int32_t color = a_Color_parse(str, default_color, &err);\n\n   if (err) {\n      BUG_MSG(\"Color \\\"%s\\\" is not in \\\"#RRGGBB\\\" format.\", str);\n   }\n   return color;\n}\n\n/*\n * Check that 'val' is composed of characters inside [A-Za-z0-9:_.-]\n * Note: ID can't have entities, but this check is enough (no '&').\n * Return value: 1 if OK, 0 otherwise.\n */\nstatic int\n Html_check_name_val(DilloHtml *html, const char *val, const char *attrname)\n{\n   if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) {\n      bool valid = *val && !strchr(val, ' ');\n\n      if (!valid) {\n         BUG_MSG(\"'%s' value \\\"%s\\\" must not be empty and must not contain \"\n                 \"spaces.\", attrname, val);\n      }\n      return valid ? 1 : 0;\n   } else {\n      int i;\n\n      for (i = 0; val[i]; ++i)\n         if (!isascii(val[i]) || !(isalnum(val[i]) || strchr(\":_.-\", val[i])))\n            break;\n\n      if (val[i] || !(isascii(val[0]) && isalpha(val[0])))\n         BUG_MSG(\"%s attribute value \\\"%s\\\" is not of the form \"\n                 \"'[A-Za-z][A-Za-z0-9:_.-]*'.\", attrname, val);\n\n      return !(val[i]);\n   }\n}\n\n/*\n * Handle DOCTYPE declaration\n *\n * Follows the convention that HTML 4.01\n * doctypes which include a full w3c DTD url are treated as\n * standards-compliant, but 4.01 without the url and HTML 4.0 and\n * earlier are not. XHTML doctypes are always standards-compliant\n * whether or not an url is present.\n *\n * Note: I'm not sure about this convention. The W3C validator\n * recognizes the \"HTML Level\" with or without the URL. The convention\n * comes from mozilla (see URLs below), but Dillo doesn't have the same\n * rendering modes, so it may be better to chose another behaviour. --Jcid\n *\n * http://www.mozilla.org/docs/web-developer/quirks/doctypes.html\n * http://lists.dillo.org/pipermail/dillo-dev/2004-October/002300.html\n *\n * This is not a full DOCTYPE parser, just enough for what Dillo uses.\n */\nstatic void Html_parse_doctype(DilloHtml *html, const char *tag, int tagsize)\n{\n   static const char HTML_SGML_sig [] = \"<!DOCTYPE HTML PUBLIC \";\n   static const char HTML20     [] = \"-//IETF//DTD HTML\";\n   static const char HTML32     [] = \"-//W3C//DTD HTML 3.2\";\n   static const char HTML40     [] = \"-//W3C//DTD HTML 4.0\";\n   static const char HTML401    [] = \"-//W3C//DTD HTML 4.01\";\n   static const char HTML401_url[] = \"http://www.w3.org/TR/html4/\";\n   static const char XHTML1     [] = \"-//W3C//DTD XHTML 1.0\";\n   static const char XHTML1_url [] = \"http://www.w3.org/TR/xhtml1/DTD/\";\n   static const char XHTML11    [] = \"-//W3C//DTD XHTML 1.1\";\n   static const char XHTML11_url[] = \"http://www.w3.org/TR/xhtml11/DTD/\";\n\n   size_t i;\n   int quote;\n   char *p, *ntag = dStrndup(tag, tagsize);\n\n   /* Tag sanitization: Collapse whitespace between tokens\n    * and replace '\\n' and '\\r' with ' ' inside quoted strings. */\n   for (i = 0, p = ntag; *p; ++p) {\n      if (isspace(*p)) {\n         for (ntag[i++] = ' '; isspace(p[1]); ++p) ;\n      } else if ((quote = *p) == '\"' || *p == '\\'') {\n         for (ntag[i++] = *p++; (ntag[i] = *p) && ntag[i++] != quote; ++p) {\n            if (*p == '\\n' || *p == '\\r')\n               ntag[i - 1] = ' ';\n            p += (p[0] == '\\r' && p[1] == '\\n') ? 1 : 0;\n         }\n      } else {\n         ntag[i++] = *p;\n      }\n      if (!*p)\n         break;\n   }\n   ntag[i] = 0;\n\n   _MSG(\"New: {%s}\\n\", ntag);\n\n   if (html->DocType != DT_NONE)\n      BUG_MSG(\"Multiple DOCTYPE declarations.\");\n\n   /* The default DT_NONE type is TagSoup */\n   if (i > strlen(HTML_SGML_sig) && // avoid out of bounds reads!\n       !dStrnAsciiCasecmp(ntag, HTML_SGML_sig, strlen(HTML_SGML_sig))) {\n      p = ntag + strlen(HTML_SGML_sig) + 1;\n      if (!strncmp(p, HTML401, strlen(HTML401)) &&\n          dStriAsciiStr(p + strlen(HTML401), HTML401_url)) {\n         html->DocType = DT_HTML;\n         html->DocTypeVersion = 4.01f;\n      } else if (!strncmp(p, XHTML1, strlen(XHTML1)) &&\n                 dStriAsciiStr(p + strlen(XHTML1), XHTML1_url)) {\n         html->DocType = DT_XHTML;\n         html->DocTypeVersion = 1.0f;\n      } else if (!strncmp(p, XHTML11, strlen(XHTML11)) &&\n                 dStriAsciiStr(p + strlen(XHTML11), XHTML11_url)) {\n         html->DocType = DT_XHTML;\n         html->DocTypeVersion = 1.1f;\n      } else if (!strncmp(p, HTML40, strlen(HTML40))) {\n         html->DocType = DT_HTML;\n         html->DocTypeVersion = 4.0f;\n      } else if (!strncmp(p, HTML32, strlen(HTML32))) {\n         html->DocType = DT_HTML;\n         html->DocTypeVersion = 3.2f;\n      } else if (!strncmp(p, HTML20, strlen(HTML20))) {\n         html->DocType = DT_HTML;\n         html->DocTypeVersion = 2.0f;\n      }\n   } else if (!dStrAsciiCasecmp(ntag, \"<!DOCTYPE html>\") ||\n              !dStrAsciiCasecmp(ntag, \"<!DOCTYPE html >\") ||\n              !dStrAsciiCasecmp(ntag,\n                           \"<!DOCTYPE html SYSTEM \\\"about:legacy-compat\\\">\") ||\n              !dStrAsciiCasecmp(ntag,\n                             \"<!DOCTYPE html SYSTEM 'about:legacy-compat'>\")) {\n      html->DocType = DT_HTML;\n      html->DocTypeVersion = 5.0f;\n   }\n   if (html->DocType == DT_NONE) {\n      html->DocType = DT_UNRECOGNIZED;\n      BUG_MSG(\"DOCTYPE not recognized: ('%s').\", ntag);\n   }\n   dFree(ntag);\n}\n\n/*\n * Handle open HTML element\n */\nstatic void Html_tag_open_html(DilloHtml *html, const char *tag, int tagsize)\n{\n   /* The IN_HTML flag will be kept set until at IN_EOF condition.\n    * This allows to handle pages with multiple or uneven HTML tags */\n\n   if (!(html->InFlags & IN_HTML))\n      html->InFlags |= IN_HTML;\n   if (html->Num_HTML < UCHAR_MAX)\n      ++html->Num_HTML;\n\n   if (html->Num_HTML > 1) {\n      BUG_MSG(\"<html> was already open.\");\n      html->ReqTagClose = true;\n   }\n}\n\n/*\n * Handle close HTML element\n */\nstatic void Html_tag_close_html(DilloHtml *html)\n{\n   _MSG(\"Html_tag_close_html: Num_HTML=%d\\n\", html->Num_HTML);\n\n  /* As some Tag soup pages use multiple HTML tags, this function\n   * gets called only on EOF and upon and extra HTML open.\n   * Also, we defer clearing the IN_HTML flag until IN_EOF */\n}\n\n/*\n * Handle open HEAD element\n */\nstatic void Html_tag_open_head(DilloHtml *html, const char *tag, int tagsize)\n{\n   if (html->InFlags & IN_BODY) {\n      BUG_MSG(\"<head> must go before the BODY section.\");\n      html->ReqTagClose = true;\n      return;\n   }\n\n   if (html->Num_HEAD < UCHAR_MAX)\n      ++html->Num_HEAD;\n   if (html->InFlags & IN_HEAD) {\n      BUG_MSG(\"<head> was already open.\");\n      html->ReqTagClose = true;\n   } else if (html->Num_HEAD > 1) {\n      BUG_MSG(\"<head> already finished -- ignoring.\");\n      html->ReqTagClose = true;\n   } else {\n      html->InFlags |= IN_HEAD;\n   }\n}\n\n/*\n * Handle close HEAD element\n * Note: HEAD is parsed once completely got.\n */\nstatic void Html_tag_close_head(DilloHtml *html)\n{\n   if (html->InFlags & IN_HEAD) {\n      if (html->Num_HEAD == 1) {\n         /* match for the well formed start of HEAD section */\n         if (html->Num_TITLE == 0)\n            BUG_MSG(\"<head> lacks <title>.\");\n\n         html->InFlags &= ~IN_HEAD;\n\n         /* charset is already set, load remote stylesheets now */\n         for (int i = 0; i < html->cssUrls->size(); i++) {\n            a_Html_load_stylesheet(html, html->cssUrls->get(i));\n         }\n      } else if (html->Num_HEAD > 1) {\n         --html->Num_HEAD;\n      }\n   } else {\n      /* not reached, see Html_tag_cleanup_at_close() */\n   }\n}\n\n/*\n * Handle open TITLE\n * calls stash init, where the title string will be stored\n */\nstatic void Html_tag_open_title(DilloHtml *html, const char *tag, int tagsize)\n{\n   /* fill the stash buffer so TITLE content can be ignored\n    * when not valid, redundant or outside HEAD section */\n   a_Html_stash_init(html);\n\n   if (html->InFlags & IN_HEAD) {\n      if (html->Num_TITLE < UCHAR_MAX)\n         ++html->Num_TITLE;\n      if (html->Num_TITLE > 1)\n         BUG_MSG(\"Redundant <title>.\");\n   } else {\n      BUG_MSG(\"<title> must be inside <head> -- ignoring.\");\n   }\n}\n\n/*\n * Handle close TITLE\n * set page-title in the browser window and in the history.\n */\nstatic void Html_tag_close_title(DilloHtml *html)\n{\n   if (html->InFlags & IN_HEAD && html->Num_TITLE == 1) {\n      /* title is only valid inside HEAD */\n      a_UIcmd_set_page_title(html->bw, html->Stash->str);\n      a_History_set_title_by_url(html->page_url, html->Stash->str);\n   }\n}\n\n/*\n * Handle open SCRIPT\n * initializes stash, where the embedded code will be stored.\n * MODE_VERBATIM is used because MODE_STASH catches entities.\n */\nstatic void Html_tag_open_script(DilloHtml *html, const char *tag, int tagsize)\n{\n   a_Html_stash_init(html);\n   S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;\n}\n\n/*\n * Handle close SCRIPT\n */\nstatic void Html_tag_close_script(DilloHtml *html)\n{\n   /* eventually the stash will be sent to an interpreter for parsing */\n}\n\n/*\n * Handle open STYLE\n * Store contents in the stash where the style sheet interpreter can get it.\n */\nstatic void Html_tag_open_style(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n\n   html->loadCssFromStash = true;\n\n   if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, \"type\"))) {\n      if (html->DocType != DT_HTML || html->DocTypeVersion <= 4.01f)\n         BUG_MSG(\"<style> requires type attribute.\");\n   } else if (dStrAsciiCasecmp(attrbuf, \"text/css\")) {\n      html->loadCssFromStash = false;\n   }\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"media\")) &&\n       dStrAsciiCasecmp(attrbuf, \"all\") && !dStriAsciiStr(attrbuf, \"screen\")) {\n      /* HTML 4.01 sec. 6.13 says that media descriptors are case-sensitive,\n       * but sec. 14.2.3 says that the attribute is case-insensitive.\n       * TODO can be a comma-separated list.\n       * TODO handheld.\n       */\n      html->loadCssFromStash = false;\n   }\n\n   a_Html_stash_init(html);\n   S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_VERBATIM;\n}\n\n/*\n * Handle close STYLE\n */\nstatic void Html_tag_close_style(DilloHtml *html)\n{\n   if (prefs.parse_embedded_css && html->loadCssFromStash)\n      html->styleEngine->parse(html, html->base_url, html->Stash->str,\n                               html->Stash->len, CSS_ORIGIN_AUTHOR);\n}\n\n/*\n * <BODY>\n */\nstatic void Html_tag_open_body(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   int32_t color;\n   style::Color *bgColor;\n   style::StyleImage *bgImage;\n   style::BackgroundRepeat bgRepeat;\n   style::BackgroundAttachment bgAttachment;\n   style::Length bgPositionX, bgPositionY;\n\n   _MSG(\"Html_tag_open_body Num_BODY=%d\\n\", html->Num_BODY);\n   if (!(html->InFlags & IN_BODY))\n      html->InFlags |= IN_BODY;\n   if (html->Num_BODY < UCHAR_MAX)\n      ++html->Num_BODY;\n\n   if (html->Num_BODY > 1) {\n      BUG_MSG(\"<body> was already open.\");\n      html->ReqTagClose = true;\n      return;\n   }\n\n   if (html->InFlags & IN_HEAD) {\n      /* if we're here, it's bad XHTML, no need to recover */\n      BUG_MSG(\"Unclosed <head>.\");\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"bgcolor\"))) {\n      color = a_Html_color_parse(html, attrbuf, -1);\n\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<body> bgcolor attribute is obsolete.\");\n\n      if (color != -1)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR,\n                                           CSS_TYPE_COLOR, color);\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"text\"))) {\n      color = a_Html_color_parse(html, attrbuf, -1);\n\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<body> text attribute is obsolete.\");\n\n      if (color != -1)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_COLOR,\n                                           CSS_TYPE_COLOR, color);\n   }\n\n   html->restyle ();\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"link\"))) {\n      html->non_css_link_color = a_Html_color_parse(html, attrbuf, -1);\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<body> link attribute is obsolete.\");\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"vlink\"))) {\n      html->non_css_visited_color = a_Html_color_parse(html, attrbuf, -1);\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<body> vlink attribute is obsolete.\");\n   }\n\n   html->dw->setStyle (html->style ());\n\n   bgColor = html->styleEngine->backgroundColor ();\n   if (bgColor)\n      HT2LT(html)->setBgColor(bgColor);\n\n   bgImage = html->styleEngine->backgroundImage (&bgRepeat, &bgAttachment,\n                                                 &bgPositionX, &bgPositionY);\n   if (bgImage)\n      HT2LT(html)->setBgImage(bgImage, bgRepeat, bgAttachment, bgPositionX,\n                              bgPositionY);\n\n   /* Determine a color for visited links.\n    * This color is computed once per page and used for immediate feedback\n    * when clicking a link.\n    * On reload style including color for visited links is computed properly\n    * according to CSS.\n    */\n   html->startElement (i_A);\n   html->styleEngine->setPseudoVisited ();\n   if (html->non_css_visited_color != -1) {\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_COLOR, CSS_TYPE_COLOR,\n                                        html->non_css_visited_color);\n   }\n   html->visited_color = html->style ()->color->getColor ();\n   html->styleEngine->endElement (i_A);\n\n   if (prefs.contrast_visited_color) {\n      /* get a color that has a \"safe distance\" from text, link and bg */\n      html->visited_color =\n         a_Color_vc(html->visited_color,\n            html->style ()->color->getColor(),\n            html->non_css_link_color,\n            html->backgroundStyle()->backgroundColor->getColor());\n   }\n\n\n   S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_BODY;\n}\n\n/*\n * BODY\n */\nstatic void Html_tag_close_body(DilloHtml *html)\n{\n   _MSG(\"Html_tag_close_body: Num_BODY=%d\\n\", html->Num_BODY);\n\n  /* As some Tag soup pages use multiple BODY tags, this function\n   * gets called only on EOF and upon and extra BODY open.\n   * Also, we defer clearing the IN_BODY flag until IN_EOF */\n}\n\n/*\n * <P>\n * TODO: what's the point between adding the parbreak before and\n *       after the push?\n */\nstatic void Html_tag_open_p(DilloHtml *html, const char *tag, int tagsize)\n{\n   CssPropertyList props;\n\n   a_Html_tag_set_align_attr (html, tag, tagsize);\n}\n\n/*\n * <FRAME>, <IFRAME>\n * TODO: This is just a temporary fix while real frame support\n *       isn't finished. Imitates lynx/w3m's frames.\n */\nstatic void Html_tag_open_frame (DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   DilloUrl *url;\n   CssPropertyList props;\n\n   if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, \"src\")))\n      return;\n\n   if (!(url = a_Html_url_new(html, attrbuf, NULL, 0)))\n      return;\n\n   if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) {\n      /* visited frame */\n      html->styleEngine->setPseudoVisited ();\n   } else {\n      /* unvisited frame */\n      html->styleEngine->setPseudoLink ();\n   }\n\n   html->styleEngine->setNonCssHint (PROPERTY_X_LINK, CSS_TYPE_INTEGER,\n                                     Html_set_new_link(html,&url));\n}\n\nstatic void\n Html_tag_content_frame (DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   char *src;\n   Textblock *textblock;\n   Widget *bullet;\n\n   textblock = HT2TB(html);\n\n   if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, \"src\")))\n      return;\n\n   src = dStrdup(attrbuf);\n\n   textblock->addParbreak (5, html->wordStyle ());\n\n   bullet = new Bullet();\n   textblock->addWidget(bullet, html->wordStyle ());\n   textblock->addSpace(html->wordStyle ());\n\n   if (D_ASCII_TOLOWER(tag[1]) == 'i') {\n      /* IFRAME usually comes with very long advertising/spying URLS,\n       * to not break rendering we will force name=\"IFRAME\" */\n      textblock->addText (\"IFRAME\", html->wordStyle ());\n\n   } else {\n      /* FRAME:\n       * If 'name' tag is present use it, if not use 'src' value */\n      if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, \"name\"))) {\n         textblock->addText (src, html->wordStyle ());\n      } else {\n         textblock->addText (attrbuf, html->wordStyle ());\n      }\n   }\n\n   textblock->addParbreak (5, html->wordStyle ());\n\n   dFree(src);\n}\n\n/*\n * <FRAMESET>\n * TODO: This is just a temporary fix while real frame support\n *       isn't finished. Imitates lynx/w3m's frames.\n */\nstatic void Html_tag_content_frameset (DilloHtml *html,\n                                    const char *tag, int tagsize)\n{\n   HT2TB(html)->addParbreak (9, html->wordStyle ());\n   HT2TB(html)->addText(\"--FRAME--\", html->wordStyle ());\n   Html_add_textblock(html, true, 5, false);\n}\n\n/*\n * <H1> | <H2> | <H3> | <H4> | <H5> | <H6>\n */\nstatic void Html_tag_open_h(DilloHtml *html, const char *tag, int tagsize)\n{\n   a_Html_tag_set_align_attr (html, tag, tagsize);\n\n   a_Html_stash_init(html);\n   S_TOP(html)->parse_mode =\n      DILLO_HTML_PARSE_MODE_STASH_AND_BODY;\n}\n\n/*\n * <BR>\n */\nstatic void Html_tag_content_br(DilloHtml *html, const char *tag, int tagsize)\n{\n   HT2TB(html)->addLinebreak (html->wordStyle ());\n}\n\n/*\n * <FONT>\n */\nstatic void Html_tag_open_font(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   char *fontFamily = NULL;\n   int32_t color;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"color\"))) {\n      if (prefs.contrast_visited_color && html->InVisitedLink) {\n         color = html->visited_color;\n      } else {\n         /* use the tag-specified color */\n         color = a_Html_color_parse(html, attrbuf, -1);\n      }\n      if (color != -1)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_COLOR,\n                                           CSS_TYPE_COLOR, color);\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"face\"))) {\n      fontFamily = dStrdup(attrbuf);\n       html->styleEngine->setNonCssHint (CSS_PROPERTY_FONT_FAMILY,\n                                         CSS_TYPE_SYMBOL, fontFamily);\n   }\n\n   dFree(fontFamily);\n}\n\n/*\n * <ABBR>\n */\nstatic void Html_tag_open_abbr(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n\n   html->styleEngine->inheritBackgroundColor ();\n\n   if (prefs.show_tooltip &&\n       (attrbuf = a_Html_get_attr(html, tag, tagsize, \"title\"))) {\n\n      html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,\n                                        attrbuf);\n   }\n}\n\n/*\n * Read image-associated tag attributes and create new image.\n */\nvoid a_Html_common_image_attrs(DilloHtml *html, const char *tag, int tagsize)\n{\n   char *width_ptr, *height_ptr;\n   const char *attrbuf;\n   CssLength l_w = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO);\n   CssLength l_h = CSS_CREATE_LENGTH(0.0, CSS_LENGTH_TYPE_AUTO);\n   int w = 0, h = 0;\n\n   if (prefs.show_tooltip &&\n       (attrbuf = a_Html_get_attr(html, tag, tagsize, \"title\"))) {\n      html->styleEngine->setNonCssHint(PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,\n                                       attrbuf);\n   }\n   width_ptr = a_Html_get_attr_wdef(html, tag, tagsize, \"width\", NULL);\n   height_ptr = a_Html_get_attr_wdef(html, tag, tagsize, \"height\", NULL);\n   // Check for malicious values\n   // TODO: the same for percentage and relative lengths.\n   if (width_ptr) {\n      l_w = a_Html_parse_length (html, width_ptr);\n      w = (int) (CSS_LENGTH_TYPE(l_w) == CSS_LENGTH_TYPE_PX ?\n                 CSS_LENGTH_VALUE(l_w) : 0);\n   }\n   if (height_ptr) {\n      l_h = a_Html_parse_length (html, height_ptr);\n      h = (int) (CSS_LENGTH_TYPE(l_h) == CSS_LENGTH_TYPE_PX ?\n                 CSS_LENGTH_VALUE(l_h) : 0);\n   }\n   /* Check for suspicious image size request that would cause\n    * an excessive amount of memory to be allocated for the\n    * image buffer.\n    * Be careful to avoid integer overflows during the checks.\n    * There is an additional check in dw/image.cc to catch cases\n    * where only one dimension is given and the image is scaled\n    * preserving its original aspect ratio.\n    * Size requests passed via CSS are also checked there.\n    */\n   if (w < 0 || h < 0 ||\n       w > IMAGE_MAX_AREA || h > IMAGE_MAX_AREA ||\n       (h > 0 && w > IMAGE_MAX_AREA / h)) {\n      dFree(width_ptr);\n      dFree(height_ptr);\n      width_ptr = height_ptr = NULL;\n      MSG(\"a_Html_common_image_attrs: suspicious image size request %d x %d\\n\",\n          w, h);\n   } else {\n      if (CSS_LENGTH_TYPE(l_w) != CSS_LENGTH_TYPE_AUTO)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_WIDTH,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, l_w);\n      if (CSS_LENGTH_TYPE(l_h) != CSS_LENGTH_TYPE_AUTO)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_HEIGHT,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, l_h);\n   }\n\n   /* TODO: we should scale the image respecting its ratio.\n    *       As the image size is not known at this time, maybe a flag\n    *       can be set to scale it later.\n   if ((width_ptr && !height_ptr) || (height_ptr && !width_ptr))\n      [...]\n   */\n\n   /* x_img is an index to a list of {url,image} pairs.\n    * We know a_Html_image_new() will use size() as its next index */\n   html->styleEngine->setNonCssHint (PROPERTY_X_IMG, CSS_TYPE_INTEGER,\n                                     html->images->size());\n\n\n   dFree(width_ptr);\n   dFree(height_ptr);\n}\n\nDilloImage *a_Html_image_new(DilloHtml *html, const char *tag, int tagsize)\n{\n   bool load_now;\n   char *alt_ptr;\n   const char *attrbuf;\n   DilloUrl *url;\n   DilloImage *image;\n\n   if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, \"src\")) ||\n       !(url = a_Html_url_new(html, attrbuf, NULL, 0)))\n      return NULL;\n\n   alt_ptr = a_Html_get_attr_wdef(html, tag, tagsize, \"alt\", NULL);\n   if (!alt_ptr || !*alt_ptr) {\n      dFree(alt_ptr);\n      alt_ptr = dStrdup(\"[IMG]\");\n   }\n\n   dw::Image *dw = new dw::Image(alt_ptr);\n   image =\n      a_Image_new(html->dw->getLayout(), (void*)(dw::core::ImgRenderer*)dw, 0);\n   \n   a_Image_ref(image);\n\n   if (HT2TB(html)->getBgColor())\n      image->bg_color = HT2TB(html)->getBgColor()->getColor();\n\n   DilloHtmlImage *hi = dNew(DilloHtmlImage, 1);\n   hi->url = url;\n   html->images->increase();\n   html->images->set(html->images->size() - 1, hi);\n\n   load_now = prefs.load_images ||\n              !dStrAsciiCasecmp(URL_SCHEME(url), \"data\") ||\n              (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached);\n\n   if (load_now && Html_load_image(html->bw, url, html->page_url, image)) {\n      // hi->image is NULL if dillo tries to load the image immediately\n      hi->image = NULL;\n      a_Image_unref(image);\n   } else {\n      // otherwise a reference is kept in html->images\n      hi->image = image;\n   }\n\n   dFree(alt_ptr);\n   return image;\n}\n\n/*\n * Tell cache to retrieve image\n */\nstatic bool Html_load_image(BrowserWindow *bw, DilloUrl *url,\n                            const DilloUrl *requester, DilloImage *Image)\n{\n   DilloWeb *Web;\n   int ClientKey;\n   /* Fill a Web structure for the cache query */\n   Web = a_Web_new(bw, url, requester);\n   Web->Image = Image;\n   a_Image_ref(Image);\n   Web->flags |= WEB_Image;\n   /* Request image data from the cache */\n   if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) {\n      a_Bw_add_client(bw, ClientKey, 0);\n      a_Bw_add_url(bw, url);\n   }\n   return ClientKey != 0;\n}\n\nstatic void Html_tag_open_img(DilloHtml *html, const char *tag, int tagsize)\n{\n   int space, border;\n   const char *attrbuf;\n\n   a_Html_common_image_attrs(html, tag, tagsize);\n\n   /* Spacing to the left and right */\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"hspace\"))) {\n      space = strtol(attrbuf, NULL, 10);\n      if (space > 0) {\n         space = CSS_CREATE_LENGTH(space, CSS_LENGTH_TYPE_PX);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_MARGIN_LEFT,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, space);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_MARGIN_RIGHT,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, space);\n      }\n   }\n\n   /* Spacing at the top and bottom */\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"vspace\"))) {\n      space = strtol(attrbuf, NULL, 10);\n      if (space > 0) {\n         space = CSS_CREATE_LENGTH(space, CSS_LENGTH_TYPE_PX);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_MARGIN_TOP,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, space);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_MARGIN_BOTTOM,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, space);\n      }\n   }\n\n   /* Border */\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"border\"))) {\n      border = strtol(attrbuf, NULL, 10);\n      if (border >= 0) {\n         border = CSS_CREATE_LENGTH(border, CSS_LENGTH_TYPE_PX);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_WIDTH,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, border);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_WIDTH,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, border);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_WIDTH,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, border);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_WIDTH,\n                                           CSS_TYPE_LENGTH_PERCENTAGE, border);\n\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_STYLE,\n                                           CSS_TYPE_ENUM, BORDER_SOLID);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_STYLE,\n                                           CSS_TYPE_ENUM, BORDER_SOLID);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_STYLE,\n                                           CSS_TYPE_ENUM, BORDER_SOLID);\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_STYLE,\n                                           CSS_TYPE_ENUM, BORDER_SOLID);\n      }\n   }\n\n}\n\n/*\n * Create a new Image struct and request the image-url to the cache\n * (If it either hits or misses, is not relevant here; that's up to the\n *  cache functions)\n */\nstatic void Html_tag_content_img(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloImage *Image;\n   DilloUrl *usemap_url;\n   const char *attrbuf;\n\n   /* This avoids loading images. Useful for viewing suspicious HTML email. */\n   if (URL_FLAGS(html->base_url) & URL_SpamSafe)\n      return;\n\n   Image = a_Html_image_new(html, tag, tagsize);\n   if (!Image)\n      return;\n\n   usemap_url = NULL;\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"usemap\")))\n      /* TODO: usemap URLs outside of the document are not used. */\n      usemap_url = a_Html_url_new(html, attrbuf, NULL, 0);\n\n   // At this point, we know that Image->ir represents an image\n   // widget. Notice that the order of the casts matters, because of\n   // multiple inheritance.\n   dw::Image *dwi = (dw::Image*)(dw::core::ImgRenderer*)Image->img_rndr;\n   HT2TB(html)->addWidget(dwi, html->style());\n   HT2TB(html)->addBreakOption (html->style (), false);\n\n   /* Image maps */\n   if (a_Html_get_attr(html, tag, tagsize, \"ismap\")) {\n      dwi->setIsMap();\n      _MSG(\"  Html_tag_open_img: server-side map (ISMAP)\\n\");\n   } else if (html->style ()->x_link != -1 &&\n              usemap_url == NULL) {\n      /* For simple links, we have to suppress the \"image_pressed\" signal.\n       * This is overridden for USEMAP images. */\n//    a_Dw_widget_set_button_sensitive (IM2DW(Image->dw), FALSE);\n   }\n\n   if (usemap_url) {\n      dwi->setUseMap(&html->maps, new ::object::String(URL_STR(usemap_url)));\n      a_Url_free (usemap_url);\n   }\n}\n\n/*\n * <map>\n */\nstatic void Html_tag_content_map(DilloHtml *html, const char *tag, int tagsize)\n{\n   char *hash_name;\n   const char *attrbuf;\n   DilloUrl *url;\n\n   if (html->InFlags & IN_MAP) {\n      BUG_MSG(\"Nested <map>.\");\n   } else {\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"name\"))) {\n         html->InFlags |= IN_MAP;\n         hash_name = dStrconcat(\"#\", attrbuf, NULL);\n         url = a_Html_url_new(html, hash_name, NULL, 0);\n         html->maps.startNewMap(new ::object::String(URL_STR(url)));\n         a_Url_free (url);\n         dFree(hash_name);\n      } else {\n         BUG_MSG(\"<map> requires name attribute.\");\n      }\n   }\n}\n\n/*\n * Handle close <MAP>\n */\nstatic void Html_tag_close_map(DilloHtml *html)\n{\n   /* This is a hack for the perhaps frivolous feature of drawing image map\n    * shapes when there is no image to display. If this map is defined after\n    * an image that has not been loaded (img != NULL), tell the image to\n    * redraw. (It will only do so if it uses a map.)\n    */\n   for (int i = 0; i < html->images->size(); i++) {\n      DilloImage *img = html->images->get(i)->image;\n\n      if (img) {\n         // At this point, we know that img->ir represents an image\n         // widget. (Really? Is this assumtion safe?) Notice that the\n         // order of the casts matters, because of multiple\n         // inheritance.\n         dw::Image *dwi = (dw::Image*)(dw::core::ImgRenderer*)img->img_rndr;\n         dwi->forceMapRedraw();\n      }\n   }\n   html->InFlags &= ~IN_MAP;\n}\n\n/*\n * Read coords in a string, returning a vector of ints.\n */\nstatic\nmisc::SimpleVector<int> *Html_read_coords(DilloHtml *html, const char *str)\n{\n   int coord;\n   const char *tail = str;\n   char *newtail = NULL;\n   misc::SimpleVector<int> *coords = new misc::SimpleVector<int> (4);\n\n   while (1) {\n      coord = strtol(tail, &newtail, 10);\n      if (coord == 0 && newtail == tail)\n         break;\n      coords->increase();\n      coords->set(coords->size() - 1, coord);\n      while (isspace(*newtail))\n         newtail++;\n      if (!*newtail)\n         break;\n      if (*newtail != ',') {\n         BUG_MSG(\"<area> coords must be integers separated by commas.\");\n      }\n      tail = newtail + 1;\n   }\n\n   return coords;\n}\n\n/*\n * <AREA>\n */\nstatic void\n Html_tag_content_area(DilloHtml *html, const char *tag, int tagsize)\n{\n   enum types {UNKNOWN, RECTANGLE, CIRCLE, POLYGON, BACKGROUND};\n   types type;\n   misc::SimpleVector<int> *coords = NULL;\n   DilloUrl* url;\n   const char *attrbuf;\n   int link = -1;\n   Shape *shape = NULL;\n\n   if (!(html->InFlags & IN_MAP)) {\n      BUG_MSG(\"<area> not inside <map>.\");\n      return;\n   }\n   attrbuf = a_Html_get_attr(html, tag, tagsize, \"shape\");\n\n   if (!attrbuf || !*attrbuf || !dStrAsciiCasecmp(attrbuf, \"rect\")) {\n      /* the default shape is a rectangle */\n      type = RECTANGLE;\n   } else if (dStrAsciiCasecmp(attrbuf, \"default\") == 0) {\n      /* \"default\" is the background */\n      type = BACKGROUND;\n   } else if (dStrAsciiCasecmp(attrbuf, \"circle\") == 0) {\n      type = CIRCLE;\n   } else if (dStrnAsciiCasecmp(attrbuf, \"poly\", 4) == 0) {\n      type = POLYGON;\n   } else {\n      BUG_MSG(\"<area> unknown shape: '%s'.\", attrbuf);\n      type = UNKNOWN;\n   }\n   if (type == RECTANGLE || type == CIRCLE || type == POLYGON) {\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"coords\"))) {\n         coords = Html_read_coords(html, attrbuf);\n\n         if (type == RECTANGLE) {\n            if (coords->size() != 4)\n               BUG_MSG(\"<area> rectangle must have four coordinate values.\");\n            if (coords->size() >= 4)\n               shape = new Rectangle(coords->get(0),\n                                     coords->get(1),\n                                     coords->get(2) - coords->get(0),\n                                     coords->get(3) - coords->get(1));\n         } else if (type == CIRCLE) {\n            if (coords->size() != 3)\n               BUG_MSG(\"<area> circle must have three coordinate values.\");\n            if (coords->size() >= 3)\n               shape = new Circle(coords->get(0), coords->get(1),\n                                  coords->get(2));\n         } else if (type == POLYGON) {\n            Polygon *poly;\n            int i;\n            if (coords->size() % 2)\n               BUG_MSG(\"<area> polygon with odd number of coordinates.\");\n            shape = poly = new Polygon();\n            for (i = 0; i < (coords->size() / 2); i++)\n               poly->addPoint(coords->get(2*i), coords->get(2*i + 1));\n         }\n         delete(coords);\n      }\n   }\n   if (shape != NULL || type == BACKGROUND) {\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"href\"))) {\n         url = a_Html_url_new(html, attrbuf, NULL, 0);\n         dReturn_if_fail ( url != NULL );\n\n         link = Html_set_new_link(html, &url);\n      }\n      if (type == BACKGROUND)\n         html->maps.setCurrentMapDefaultLink(link);\n      else\n         html->maps.addShapeToCurrentMap(shape, link);\n   }\n}\n\n/*\n * <OBJECT>\n * Simply provide a link if the object is something downloadable.\n */\nstatic void Html_tag_open_object(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloUrl *url, *base_url = NULL;\n   const char *attrbuf;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"codebase\"))) {\n      base_url = a_Html_url_new(html, attrbuf, NULL, 0);\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"data\"))) {\n      url = a_Html_url_new(html, attrbuf,\n                           URL_STR(base_url), (base_url != NULL));\n      dReturn_if_fail ( url != NULL );\n\n      if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) {\n         html->styleEngine->setPseudoVisited ();\n      } else {\n         html->styleEngine->setPseudoLink ();\n      }\n\n      html->styleEngine->setNonCssHint(PROPERTY_X_LINK, CSS_TYPE_INTEGER,\n                                       Html_set_new_link(html, &url));\n   }\n   a_Url_free(base_url);\n}\n\nstatic void Html_tag_content_object(DilloHtml *html, const char *tag,\n                                    int tagsize)\n{\n   if (a_Html_get_attr(html, tag, tagsize, \"data\"))\n      HT2TB(html)->addText(\"[OBJECT]\", html->wordStyle ());\n}\n\n/*\n * <VIDEO>\n * Provide a link to the video.\n */\nstatic void Html_tag_open_video(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloUrl *url;\n   const char *attrbuf;\n\n   if (html->InFlags & IN_MEDIA) {\n      MSG(\"<video> not handled when already inside a media element.\\n\");\n      return;\n   }\n   /* TODO: poster attr */\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"src\"))) {\n      url = a_Html_url_new(html, attrbuf, NULL, 0);\n      dReturn_if_fail ( url != NULL );\n\n      if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) {\n         html->styleEngine->setPseudoVisited ();\n      } else {\n         html->styleEngine->setPseudoLink ();\n      }\n\n      html->styleEngine->setNonCssHint(PROPERTY_X_LINK, CSS_TYPE_INTEGER,\n                                       Html_set_new_link(html, &url));\n\n      HT2TB(html)->addText(\"[VIDEO]\", html->wordStyle ());\n   }\n   html->InFlags |= IN_MEDIA;\n}\n\n/*\n * <AUDIO>\n * Provide a link to the audio.\n */\nstatic void Html_tag_open_audio(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloUrl *url;\n   const char *attrbuf;\n\n   if (html->InFlags & IN_MEDIA) {\n      MSG(\"<audio> not handled when already inside a media element.\\n\");\n      return;\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"src\"))) {\n      url = a_Html_url_new(html, attrbuf, NULL, 0);\n      dReturn_if_fail ( url != NULL );\n\n      if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) {\n         html->styleEngine->setPseudoVisited ();\n      } else {\n         html->styleEngine->setPseudoLink ();\n      }\n\n      html->styleEngine->setNonCssHint(PROPERTY_X_LINK, CSS_TYPE_INTEGER,\n                                       Html_set_new_link(html, &url));\n\n      HT2TB(html)->addText(\"[AUDIO]\", html->wordStyle ());\n   }\n   html->InFlags |= IN_MEDIA;\n}\n\n/*\n * <SOURCE>\n * Media resource; provide a link to its address.\n */\nstatic void Html_tag_open_source(DilloHtml *html, const char *tag,\n                                    int tagsize)\n{\n   const char *attrbuf;\n\n   if (!(html->InFlags & IN_MEDIA)) {\n      // Can also be inside a picture element.\n      // BUG_MSG(\"<source> not inside a media element.\");\n      return;\n   }\n   if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, \"src\"))) {\n      BUG_MSG(\"<source> requires src attribute.\");\n      return;\n   } else {\n      DilloUrl *url = a_Html_url_new(html, attrbuf, NULL, 0);\n\n      dReturn_if_fail ( url != NULL );\n\n      if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) {\n         html->styleEngine->setPseudoVisited ();\n      } else {\n         html->styleEngine->setPseudoLink ();\n      }\n      html->styleEngine->setNonCssHint(PROPERTY_X_LINK, CSS_TYPE_INTEGER,\n                                       Html_set_new_link(html, &url));\n   }\n}\n\nstatic void Html_tag_content_source(DilloHtml *html, const char *tag,\n                                    int tagsize)\n{\n   if ((html->InFlags & IN_MEDIA) && a_Html_get_attr(html, tag, tagsize, \"src\")) {\n      HT2TB(html)->addText(\"[MEDIA SOURCE]\", html->wordStyle ());\n      if(a_Html_get_attr(html, tag, tagsize, \"label\")) {\n         HT2TB(html)->addText(\" \", html->wordStyle ());\n         HT2TB(html)->addText(a_Html_get_attr(html, tag, tagsize, \"label\"),\n                              html->wordStyle ());\n      }\n      if(a_Html_get_attr(html, tag, tagsize, \"type\")) {\n         HT2TB(html)->addText(\" [\", html->wordStyle ());\n         HT2TB(html)->addText(a_Html_get_attr(html, tag, tagsize, \"type\"),\n                              html->wordStyle ());\n         HT2TB(html)->addText(\"]\", html->wordStyle ());\n      }\n      HT2TB(html)->addLinebreak (html->wordStyle ());\n   }\n}\n\n/*\n * Media (AUDIO/VIDEO) close function\n */\nstatic void Html_tag_close_media(DilloHtml *html)\n{\n   html->InFlags &= ~IN_MEDIA;\n}\n\n/*\n * <EMBED>\n * Provide a link to embedded content.\n */\nstatic void Html_tag_open_embed(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"src\"))) {\n      DilloUrl *url = a_Html_url_new(html, attrbuf, NULL, 0);\n\n      dReturn_if_fail ( url != NULL );\n\n      if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) {\n         html->styleEngine->setPseudoVisited ();\n      } else {\n         html->styleEngine->setPseudoLink ();\n      }\n\n      html->styleEngine->setNonCssHint(PROPERTY_X_LINK, CSS_TYPE_INTEGER,\n                                       Html_set_new_link(html, &url));\n   }\n}\n\nstatic void Html_tag_content_embed(DilloHtml *html,const char *tag,int tagsize)\n{\n   if (a_Html_get_attr(html, tag, tagsize, \"src\"))\n      HT2TB(html)->addText(\"[EMBED]\", html->wordStyle ());\n}\n\n/*\n * Test and extract the link from a javascript instruction.\n */\nstatic const char* Html_get_javascript_link(DilloHtml *html)\n{\n   size_t i;\n   char ch, *p1, *p2;\n   Dstr *Buf = html->attr_data;\n\n   if (dStrnAsciiCasecmp(\"javascript\", Buf->str, 10) == 0) {\n      i = strcspn(Buf->str, \"'\\\"\");\n      ch = Buf->str[i];\n      if ((ch == '\"' || ch == '\\'') &&\n          (p2 = strchr(Buf->str + i + 1 , ch))) {\n         p1 = Buf->str + i;\n         BUG_MSG(\"Link depends on javascript().\");\n         dStr_truncate(Buf, p2 - Buf->str);\n         dStr_erase(Buf, 0, p1 - Buf->str + 1);\n      }\n   }\n   return Buf->str;\n}\n\n/*\n * Register an anchor for this page.\n */\nstatic void Html_add_anchor(DilloHtml *html, const char *name)\n{\n   _MSG(\"Registering ANCHOR: %s\\n\", name);\n   if (!HT2TB(html)->addAnchor (name, html->style ()))\n      BUG_MSG(\"Anchor names must be unique within the document (\\\"%s\\\").\",\n              name);\n   /*\n    * According to Sec. 12.2.1 of the HTML 4.01 spec, \"anchor names that\n    * differ only in case may not appear in the same document\", but\n    * \"comparisons between fragment identifiers and anchor names must be\n    * done by exact (case-sensitive) match.\" We ignore the case issue and\n    * always test for exact matches. Moreover, what does uppercase mean\n    * for Unicode characters outside the ASCII range?\n    */\n}\n\n/*\n * <A>\n */\nstatic void Html_tag_open_a(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloUrl *url;\n   const char *attrbuf;\n\n   /* TODO: add support for MAP with A HREF */\n   html->InFlags |= IN_A;\n   if (html->InFlags & IN_MAP)\n      Html_tag_content_area(html, tag, tagsize);\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"href\"))) {\n      /* if it's a javascript link, extract the reference. */\n      if (D_ASCII_TOLOWER(attrbuf[0]) == 'j')\n         attrbuf = Html_get_javascript_link(html);\n\n      url = a_Html_url_new(html, attrbuf, NULL, 0);\n      dReturn_if_fail ( url != NULL );\n\n      if (a_Capi_get_flags_with_redirection(url) & CAPI_IsCached) {\n         html->InVisitedLink = true;\n         html->styleEngine->setPseudoVisited ();\n         if (html->non_css_visited_color != -1)\n            html->styleEngine->setNonCssHint(CSS_PROPERTY_COLOR,\n                                             CSS_TYPE_COLOR,\n                                             html->non_css_visited_color);\n      } else {\n         html->styleEngine->setPseudoLink ();\n         if (html->non_css_link_color != -1)\n            html->styleEngine->setNonCssHint(CSS_PROPERTY_COLOR,\n                                             CSS_TYPE_COLOR,\n                                             html->non_css_link_color);\n      }\n\n      html->styleEngine->setNonCssHint (PROPERTY_X_LINK, CSS_TYPE_INTEGER,\n                                        Html_set_new_link(html, &url));\n   }\n   if (prefs.show_tooltip &&\n       (attrbuf = a_Html_get_attr(html, tag, tagsize, \"title\"))) {\n      html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,\n                                        attrbuf);\n   }\n\n   html->styleEngine->inheritBackgroundColor ();\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"name\"))) {\n      char *nameVal;\n      const char *id = html->styleEngine->getId ();\n\n      if (prefs.show_extra_warnings)\n         Html_check_name_val(html, attrbuf, \"name\");\n\n      nameVal = a_Url_decode_hex_str(attrbuf);\n\n      if (nameVal) {\n         /* We compare the \"id\" value with the url-decoded \"name\" value */\n         if (!id || strcmp(nameVal, id)) {\n            if (id)\n               BUG_MSG(\"In <a>, id ('%s') and name ('%s') attributes differ.\",\n                        id, nameVal);\n            Html_add_anchor(html, nameVal);\n         }\n\n         dFree(nameVal);\n      }\n   }\n}\n\n/*\n * <A> close function\n */\nstatic void Html_tag_close_a(DilloHtml *html)\n{\n   html->InFlags &= ~IN_A;\n   html->InVisitedLink = false;\n}\n\n/*\n * <BLOCKQUOTE>\n */\nstatic void Html_tag_open_blockquote(DilloHtml *html,\n                                     const char *tag, int tagsize)\n{\n   Html_add_textblock(html, true, 9, false);\n}\n\n/*\n * <Q>\n */\nstatic void Html_tag_open_q(DilloHtml *html, const char *tag, int tagsize)\n{\n   html->styleEngine->inheritBackgroundColor ();\n}\n\nstatic void Html_tag_content_q(DilloHtml *html, const char *tag, int tagsize)\n{\n   /*\n    * Left Double Quotation Mark, which is wrong in many cases, but\n    * should at least be widely recognized.\n    */\n   const char *U201C = \"\\xe2\\x80\\x9c\";\n\n   HT2TB(html)->addText (U201C, html->wordStyle ());\n}\n\n/*\n * </Q>\n */\nstatic void Html_tag_close_q(DilloHtml *html)\n{\n   /* Right Double Quotation Mark */\n   const char *U201D = \"\\xe2\\x80\\x9d\";\n\n   HT2TB(html)->addText (U201D, html->wordStyle ());\n}\n\n/*\n * Handle the <UL> tag.\n */\nstatic void Html_tag_open_ul(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   ListStyleType list_style_type;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"type\"))) {\n\n      /* list_style_type explicitly defined */\n      if (dStrAsciiCasecmp(attrbuf, \"disc\") == 0)\n         list_style_type = LIST_STYLE_TYPE_DISC;\n      else if (dStrAsciiCasecmp(attrbuf, \"circle\") == 0)\n         list_style_type = LIST_STYLE_TYPE_CIRCLE;\n      else if (dStrAsciiCasecmp(attrbuf, \"square\") == 0)\n         list_style_type = LIST_STYLE_TYPE_SQUARE;\n      else\n         /* invalid value */\n         list_style_type = LIST_STYLE_TYPE_DISC;\n\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_LIST_STYLE_TYPE,\n                                        CSS_TYPE_ENUM, list_style_type);\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<ul> type attribute is obsolete.\");\n   }\n\n   S_TOP(html)->list_type = HTML_LIST_UNORDERED;\n   S_TOP(html)->list_number = 0;\n   S_TOP(html)->ref_list_item = NULL;\n}\n\n/*\n * Handle the <DIR> or <MENU> tag.\n * (Deprecated and almost the same as <UL>)\n */\nstatic void Html_tag_open_dir(DilloHtml *html, const char *tag, int tagsize)\n{\n   html->styleEngine->inheritBackgroundColor ();\n   HT2TB(html)->addParbreak (9, html->wordStyle ());\n\n   S_TOP(html)->list_type = HTML_LIST_UNORDERED;\n   S_TOP(html)->list_number = 0;\n   S_TOP(html)->ref_list_item = NULL;\n\n   if (prefs.show_extra_warnings)\n      BUG_MSG(\"Obsolete list type; use <ul> instead.\");\n}\n\n/*\n * Handle the <MENU> tag.\n */\nstatic void Html_tag_open_menu(DilloHtml *html, const char *tag, int tagsize)\n{\n   /* In another bit of ridiculous mess from the HTML5 world, the menu\n    * element, which was deprecated in HTML4:\n    * - does not appear at all in W3C's HTML5 spec\n    * - appears in WHATWG's HTML5 doc and the W3C's 5.1 draft, where it\n    *   means something totally different than it did in the old days\n    *   (now it's for popup menus and toolbar menus rather than being a\n    *   sort of list).\n    */\n   if (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f))\n      Html_tag_open_dir(html, tag, tagsize);\n}\n\n/*\n * Handle the <OL> tag.\n */\nstatic void Html_tag_open_ol(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   int n = 1;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"type\"))) {\n      ListStyleType listStyleType = LIST_STYLE_TYPE_DECIMAL;\n\n      if (*attrbuf == '1')\n         listStyleType = LIST_STYLE_TYPE_DECIMAL;\n      else if (*attrbuf == 'a')\n         listStyleType = LIST_STYLE_TYPE_LOWER_ALPHA;\n      else if (*attrbuf == 'A')\n         listStyleType = LIST_STYLE_TYPE_UPPER_ALPHA;\n      else if (*attrbuf == 'i')\n         listStyleType = LIST_STYLE_TYPE_LOWER_ROMAN;\n      else if (*attrbuf == 'I')\n         listStyleType = LIST_STYLE_TYPE_UPPER_ROMAN;\n\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_LIST_STYLE_TYPE,\n                                        CSS_TYPE_ENUM, listStyleType);\n   }\n\n   S_TOP(html)->list_type = HTML_LIST_ORDERED;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"start\")) &&\n       (n = (int) strtol(attrbuf, NULL, 10)) < 0) {\n      BUG_MSG(\"Illegal '-' character in START attribute; Starting from 0.\");\n      n = 0;\n   }\n   S_TOP(html)->list_number = n;\n   S_TOP(html)->ref_list_item = NULL;\n}\n\n/*\n * Handle the <LI> tag.\n */\nstatic void Html_tag_open_li(DilloHtml *html, const char *tag, int tagsize)\n{\n   Style *style = html->style ();\n   int *list_number;\n   const char *attrbuf;\n\n   if (S_TOP(html)->list_type == HTML_LIST_NONE &&\n       !(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) {\n      /* In WHATWG's HTML5 and W3C's HTML 5.1, LI can appear within MENUs\n       * of the toolbar type.\n       */\n      BUG_MSG(\"<li> outside <ul> or <ol>.\");\n   }\n\n   html->InFlags |= IN_LI;\n\n   /* Get our parent tag's variables (used as state storage) */\n   list_number = &html->stack->getRef(html->stack->size()-2)->list_number;\n\n   if (style->listStyleType >= LIST_STYLE_TYPE_DECIMAL) {\n      // ordered\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"value\")) &&\n          (*list_number = strtol(attrbuf, NULL, 10)) < 0) {\n         BUG_MSG(\"Illegal negative list value attribute; Starting from 0.\");\n         *list_number = 0;\n      }\n   }\n}\n\n/*\n * Close <LI>\n */\nstatic void Html_tag_close_li(DilloHtml *html)\n{\n   html->InFlags &= ~IN_LI;\n   ((ListItem *)html->dw)->flush ();\n}\n\n/*\n * <HR>\n */\nstatic void Html_tag_open_hr(DilloHtml *html, const char *tag, int tagsize)\n{\n   char *width_ptr;\n   const char *attrbuf;\n   int32_t size = 0;\n\n   width_ptr = a_Html_get_attr_wdef(html, tag, tagsize, \"width\", NULL);\n   if (width_ptr) {\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<hr> width attribute is obsolete.\");\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE,\n                                        a_Html_parse_length (html, width_ptr));\n      dFree(width_ptr);\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"size\"))) {\n      size = strtol(attrbuf, NULL, 10);\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<hr> size attribute is obsolete.\");\n   }\n\n   a_Html_tag_set_align_attr(html, tag, tagsize);\n\n   /* TODO: evaluate attribute */\n   if (a_Html_get_attr(html, tag, tagsize, \"noshade\")) {\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<hr> noshade attribute is obsolete.\");\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_SOLID);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_SOLID);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_SOLID);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_SOLID);\n\n      if (size <= 0)\n         size = 1;\n   }\n\n   if (size > 0) {\n      CssLength size_top = CSS_CREATE_LENGTH ((size+1)/2, CSS_LENGTH_TYPE_PX);\n      CssLength size_bottom = CSS_CREATE_LENGTH (size / 2, CSS_LENGTH_TYPE_PX);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, size_top);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, size_top);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE,\n                                        size_bottom);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE,\n                                        size_bottom);\n   }\n\n}\n\nstatic void Html_tag_content_hr(DilloHtml *html, const char *tag, int tagsize)\n{\n   Widget *hruler;\n   HT2TB(html)->addParbreak (5, html->wordStyle ());\n\n   hruler = new Ruler();\n   hruler->setStyle (html->style ());\n   HT2TB(html)->addWidget (hruler, html->style ());\n   HT2TB(html)->addParbreak (5, html->wordStyle ());\n}\n\n/*\n * <DL>\n */\nstatic void Html_tag_open_dl(DilloHtml *html, const char *tag, int tagsize)\n{\n   /* may want to actually do some stuff here. */\n   html->styleEngine->inheritBackgroundColor ();\n   HT2TB(html)->addParbreak (9, html->wordStyle ());\n}\n\n/*\n * <DT>\n */\nstatic void Html_tag_open_dt(DilloHtml *html, const char *tag, int tagsize)\n{\n   html->styleEngine->inheritBackgroundColor ();\n   HT2TB(html)->addParbreak (9, html->wordStyle ());\n}\n\n/*\n * <DD>\n */\nstatic void Html_tag_open_dd(DilloHtml *html, const char *tag, int tagsize)\n{\n   Html_add_textblock(html, true, 9, false);\n}\n\n/*\n * <PRE>\n */\nstatic void Html_tag_open_pre(DilloHtml *html, const char *tag, int tagsize)\n{\n   html->styleEngine->inheritBackgroundColor ();\n   HT2TB(html)->addParbreak (9, html->wordStyle ());\n\n   html->InFlags |= IN_PRE;\n}\n\n/*\n * Custom close for <PRE>\n */\nstatic void Html_tag_close_pre(DilloHtml *html)\n{\n   html->InFlags &= ~IN_PRE;\n}\n\n/*\n * Check whether a tag is in the \"excluding\" element set for PRE\n * Excl. Set = {IMG, OBJECT, APPLET, BIG, SMALL, SUB, SUP, FONT, BASEFONT}\n */\nstatic int Html_tag_pre_excludes(DilloHtml *html, int tag_idx)\n{\n   if (!(html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)) {\n      /* HTML5 doesn't say anything about excluding elements */\n      const char *es_set[] = {\"img\", \"object\", \"applet\", \"big\", \"small\", \"sub\",\n                              \"sup\", \"font\", \"basefont\", NULL};\n      static int ei_set[10], i;\n\n      /* initialize array */\n      if (!ei_set[0])\n         for (i = 0; es_set[i]; ++i)\n            ei_set[i] = a_Html_tag_index(es_set[i]);\n\n      for (i = 0; ei_set[i]; ++i)\n         if (tag_idx == ei_set[i])\n            return 1;\n   }\n   return 0;\n}\n\n/*\n * Update the document's content type information based on meta tag data.\n */\nstatic void Html_update_content_type(DilloHtml *html, const char *content)\n{\n   const char *new_content = a_Capi_set_content_type(html->page_url, content,\n                                                     \"meta\");\n   /* Cannot ask cache whether the content type was changed, as\n    * this code in another bw might have already changed it for us.\n    */\n   if (a_Misc_content_type_cmp(html->content_type, new_content)) {\n      html->stop_parser = true; /* The cache buffer is no longer valid */\n      a_UIcmd_repush(html->bw);\n   }\n}\n\n/*\n * Handle <META>\n * We do not support http-equiv=refresh with delay>0 because it's\n * non standard, (the HTML 4.01 SPEC recommends explicitly to avoid it).\n * More info at:\n *   http://lists.w3.org/Archives/Public/www-html/2000Feb/thread.html#msg232\n * Instant client-side redirects (delay=0) are supported:\n *   http://www.w3.org/TR/2008/NOTE-WCAG20-TECHS-20081211/H76.html\n *\n * TODO: Note that we're sending custom HTML while still IN_HEAD. This\n * is a hackish way to put the message. A much cleaner approach is to\n * build a custom widget for it.\n */\nstatic void Html_tag_open_meta(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char meta_template[] =\n\"<table width='100%%'><tr><td bgcolor='#ee0000'>Warning:</td>\\n\"\n\" <td bgcolor='#8899aa' width='100%%'>\\n\"\n\" This page uses the NON-STANDARD meta refresh tag.<br> The HTML 4.01 SPEC\\n\"\n\" (sec 7.4.4) recommends explicitly to avoid it.</td></tr>\\n\"\n\" <tr><td bgcolor='#a0a0a0' colspan='2'>The author wanted you to go\\n\"\n\" <a href='%s'>here</a>%s</td></tr></table><br>\\n\";\n\n   const char *p, *equiv, *charset, *content;\n   char delay_str[64], *mr_url;\n   DilloUrl *new_url;\n   int delay;\n\n   /* only valid inside HEAD */\n   if (!(html->InFlags & IN_HEAD)) {\n      if (!((html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) &&\n            a_Html_get_attr(html, tag, tagsize, \"itemprop\"))) {\n         /* With the HTML 5.1 draft spec, meta with itemprop may appear\n          * in the body.\n          */\n         BUG_MSG(\"This <meta> element must be inside the HEAD section.\");\n      }\n      return;\n   }\n\n   if ((equiv = a_Html_get_attr(html, tag, tagsize, \"http-equiv\"))) {\n      if (!dStrAsciiCasecmp(equiv, \"refresh\") &&\n          (content = a_Html_get_attr(html, tag, tagsize, \"content\"))) {\n\n         /* Get delay, if present, and make a message with it */\n         if ((delay = strtol(content, NULL, 0))) {\n            snprintf(delay_str, 64, \" after %d second%s.\",\n                     delay, (delay > 1) ? \"s\" : \"\");\n         } else {\n            snprintf(delay_str, 64, \".\");\n         }\n         /* Skip to anything after \"URL=\" or \";\" if \"URL=\" is not found */\n         if ((p = dStriAsciiStr(content, \"url=\")))\n            content = p + strlen(\"url=\");\n         else if ((p = strstr(content, \";\")))\n            content = p + strlen(\";\");\n         /* Handle the case of a quoted URL */\n         if (*content == '\"' || *content == '\\'') {\n            if ((p = strchr(content + 1, *content)))\n               mr_url = dStrndup(content + 1, p - content - 1);\n            else\n               mr_url = dStrdup(content + 1);\n         } else {\n            mr_url = dStrdup(content);\n         }\n         new_url = a_Html_url_new(html, mr_url, NULL, 0);\n\n         if (a_Url_cmp(html->base_url, new_url) == 0) {\n            /* redirection loop, or empty url string: ignore */\n            BUG_MSG(\"<meta> refresh: %s.\",\n                    *mr_url ? \"redirection loop\" : \"no target URL\");\n         } else if (delay == 0) {\n            /* zero-delay redirection */\n            html->stop_parser = true;\n            if (URL_FLAGS(html->base_url) & URL_SpamSafe) {\n               a_UIcmd_set_msg(html->bw,\n                  \"WARNING: local URL with META refresh.  Aborting.\");\n            } else if (a_Capi_dpi_verify_request(html->bw, new_url)) {\n               a_UIcmd_redirection0((void*)html->bw, new_url);\n            }\n         } else {\n            /* Send a custom HTML message.\n             * TODO: This is a hairy hack,\n             *       It'd be much better to build a widget. */\n            Dstr *ds_msg = dStr_sized_new(256);\n            dStr_sprintf(ds_msg, meta_template, URL_STR(new_url), delay_str);\n            {\n               int o_InFlags = html->InFlags;\n               int o_TagSoup = html->TagSoup;\n               html->InFlags = IN_BODY + IN_META_HACK;\n               html->TagSoup = false;\n               Html_write_raw(html, ds_msg->str, ds_msg->len, 0, &Html_process_tag);\n               html->TagSoup = o_TagSoup;\n               html->InFlags = o_InFlags;\n            }\n            dStr_free(ds_msg, 1);\n         }\n         a_Url_free(new_url);\n         dFree(mr_url);\n\n      } else if (!dStrAsciiCasecmp(equiv, \"content-type\") &&\n                 (content = a_Html_get_attr(html, tag, tagsize, \"content\"))) {\n         _MSG(\"Html_tag_open_meta: content={%s}\\n\", content);\n         Html_update_content_type(html, content);\n      }\n   } else if (html->DocType == DT_HTML && html->DocTypeVersion == 5.0f &&\n              (charset = a_Html_get_attr(html, tag, tagsize, \"charset\"))) {\n      char *content = dStrconcat(\"text/html; charset=\", charset, NULL);\n\n      Html_update_content_type(html, content);\n      dFree(content);\n   }\n}\n\n/*\n * Called by the network engine when a stylesheet has new data.\n */\nstatic void Html_css_load_callback(int Op, CacheClient_t *Client)\n{\n   _MSG(\"Html_css_load_callback: Op=%d\\n\", Op);\n   if (Op) { /* EOF */\n      BrowserWindow *bw = ((DilloWeb *)Client->Web)->bw;\n      /* Repush when we've got them all */\n      if (--bw->NumPendingStyleSheets == 0)\n         a_UIcmd_repush(bw);\n   }\n}\n\n/*\n * Tell cache to retrieve a stylesheet\n */\nvoid a_Html_load_stylesheet(DilloHtml *html, DilloUrl *url)\n{\n   char *data;\n   int len;\n\n   dReturn_if (url == NULL || ! prefs.load_stylesheets);\n\n   _MSG(\"Html_load_stylesheet: \");\n   if ((a_Capi_get_flags_with_redirection(url) & CAPI_Completed) &&\n       a_Capi_get_buf(url, &data, &len)) {\n      _MSG(\"cached URL=%s len=%d\", URL_STR(url), len);\n      if (strncmp(\"@charset \\\"\", data, 10) == 0) {\n         char *endq = strchr(data+10, '\"');\n\n         if (endq && (endq - data <= 51)) {\n            /* IANA limits charset names to 40 characters */\n            char *content_type;\n\n            *endq = '\\0';\n            content_type = dStrconcat(\"text/css; charset=\", data+10, NULL);\n            *endq = '\"';\n            a_Capi_unref_buf(url);\n            a_Capi_set_content_type(url, content_type, \"meta\");\n            dFree(content_type);\n            a_Capi_get_buf(url, &data, &len);\n         }\n      }\n      html->styleEngine->parse(html, url, data, len, CSS_ORIGIN_AUTHOR);\n      a_Capi_unref_buf(url);\n   } else {\n      /* Fill a Web structure for the cache query */\n      int ClientKey;\n      DilloWeb *Web = a_Web_new(html->bw, url, html->page_url);\n      Web->flags |= WEB_Stylesheet;\n      if ((ClientKey = a_Capi_open_url(Web, Html_css_load_callback, NULL))) {\n         ++html->bw->NumPendingStyleSheets;\n         a_Bw_add_client(html->bw, ClientKey, 0);\n         a_Bw_add_url(html->bw, url);\n         MSG(\"NumPendingStyleSheets=%d\\n\", html->bw->NumPendingStyleSheets);\n      }\n   }\n   _MSG(\"\\n\");\n}\n\n/*\n * Parse the LINK element (Only CSS stylesheets by now).\n * (If it either hits or misses, is not relevant here; that's up to the\n *  cache functions)\n *\n * TODO: How will we know when to use \"handheld\"? Ask the html->bw->ui for\n * screen dimensions, or a dillorc preference.\n */\nstatic void Html_tag_open_link(DilloHtml *html, const char *tag, int tagsize)\n{\n   DilloUrl *url;\n   const char *attrbuf;\n\n   //char *tag_str = dStrndup(tag, tagsize);\n   //MSG(\"Html_tag_open_link(): %s\\n\", tag_str);\n   //dFree(tag_str);\n\n   /* When viewing suspicious HTML email, don't load LINK */\n   dReturn_if (URL_FLAGS(html->base_url) & URL_SpamSafe);\n\n   /* Ignore LINK outside HEAD */\n   if (!(html->InFlags & IN_HEAD)) {\n      if (!((html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f) &&\n            a_Html_get_attr(html, tag, tagsize, \"itemprop\"))) {\n         /* With the HTML 5.1 draft spec, link with itemprop may appear\n          * in the body.\n          */\n         BUG_MSG(\"This <link> element must be inside the HEAD section.\");\n      }\n      return;\n   }\n   /* Remote stylesheets enabled? */\n   dReturn_if_fail (prefs.load_stylesheets);\n   /* CSS stylesheet link */\n   if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, \"rel\")) ||\n       dStrAsciiCasecmp(attrbuf, \"stylesheet\"))\n      return;\n\n   /* IMPLIED attributes? */\n   if (((attrbuf = a_Html_get_attr(html, tag, tagsize, \"type\")) &&\n        dStrAsciiCasecmp(attrbuf, \"text/css\")) ||\n       ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"media\")) &&\n        !dStriAsciiStr(attrbuf, \"screen\") && dStrAsciiCasecmp(attrbuf, \"all\")))\n      return;\n\n   if (!(attrbuf = a_Html_get_attr(html, tag, tagsize, \"href\")) ||\n       !(url = a_Html_url_new(html, attrbuf, NULL, 0)))\n      return;\n\n   _MSG(\"  Html_tag_open_link(): addCssUrl %s\\n\", URL_STR(url));\n\n   html->addCssUrl(url);\n   a_Url_free(url);\n}\n\n/*\n * Set the Document Base URI\n */\nstatic void Html_tag_open_base(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   DilloUrl *BaseUrl;\n\n   if (html->InFlags & IN_HEAD) {\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"href\"))) {\n         bool_t html5 = html->DocType == DT_HTML &&\n                        html->DocTypeVersion >= 5.0f;\n\n         BaseUrl = html5 ? a_Html_url_new(html, attrbuf, NULL, 0) :\n                           a_Html_url_new(html, attrbuf, \"\", 1);\n\n         if (html5 || URL_SCHEME_(BaseUrl)) {\n            /* Pass the URL_SpamSafe flag to the new base url */\n            a_Url_set_flags(\n               BaseUrl, URL_FLAGS(html->base_url) & URL_SpamSafe);\n            a_Url_free(html->base_url);\n            html->base_url = BaseUrl;\n         } else {\n            BUG_MSG(\"<base> URI is relative (it MUST be absolute).\");\n            a_Url_free(BaseUrl);\n         }\n      }\n   } else {\n      BUG_MSG(\"<base> not inside HEAD section.\");\n   }\n}\n\nstatic void Html_tag_open_default(DilloHtml *html,const char *tag,int tagsize)\n{\n   html->styleEngine->inheritBackgroundColor();\n}\n\n/*\n * <SPAN>\n */\nstatic void Html_tag_open_span(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n\n   html->styleEngine->inheritBackgroundColor();\n\n   if (prefs.show_tooltip &&\n       (attrbuf = a_Html_get_attr(html, tag, tagsize, \"title\"))) {\n\n      html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,\n                                        attrbuf);\n   }\n}\n\n/*\n * html5 sectioning stuff: article aside nav section header footer\n */\nstatic void Html_tag_open_sectioning(DilloHtml *html, const char *tag,\n                                     int tagsize)\n{\n   const char *attrbuf;\n\n   if (prefs.show_tooltip &&\n       (attrbuf = a_Html_get_attr(html, tag, tagsize, \"title\"))) {\n\n      html->styleEngine->setNonCssHint (PROPERTY_X_TOOLTIP, CSS_TYPE_STRING,\n                                        attrbuf);\n   }\n}\n\n/*\n * <DIV> (TODO: make a complete implementation)\n */\nstatic void Html_tag_open_div(DilloHtml *html, const char *tag, int tagsize)\n{\n   a_Html_tag_set_align_attr (html, tag, tagsize);\n   Html_tag_open_sectioning(html, tag, tagsize);\n}\n\n/*\n * Default close for paragraph tags - pop the stack and break.\n */\nstatic void Html_tag_close_par(DilloHtml *html)\n{\n   HT2TB(html)->addParbreak (9, html->wordStyle ());\n}\n\n/*\n * <WBR> \"The wbr element represents a line break opportunity.\"\n */\nstatic void Html_tag_content_wbr(DilloHtml *html, const char *tag, int tagsize)\n{\n   HT2TB(html)->addBreakOption(html->wordStyle (), true);\n}\n\n\n/*\n * Function index for the open, content, and close functions for each tag\n * (Alphabetically sorted for a binary search).\n * The open and close functions are always called. They are used for style\n * handling and HTML bug reporting.\n * Content creation (e.g. adding new widgets or text) is done in the content\n * function, which is not called in the display:none case.\n * Note: many tags don't need a content function (e.g. <div>, <span>, ...).\n *\n * Explanation for the 'Flags' field:\n *\n *   {\"address\", B8(01110), ...}\n *                  |||||\n *                  ||||`-- inline/block element (1/0 resp.)\n *                  |||`--- inline container\n *                  ||`---- block container\n *                  |`----- body element\n *                  `------ head element\n *\n *   Notes:\n *     - The upper two bits are not used yet.\n *     - Empty elements have both inline and block container clear.\n *       (flow have both set)\n */\n\nstatic const TagInfo Tags[] = {\n {\"a\", B8(01011),'R', Html_tag_open_a, NULL, Html_tag_close_a},\n {\"abbr\", B8(01011),'R', Html_tag_open_abbr, NULL, NULL},\n /* acronym 010101 -- obsolete in HTML5 */\n {\"address\", B8(01110),'R', Html_tag_open_default, NULL, Html_tag_close_par},\n {\"area\", B8(01001),'F', Html_tag_open_default, Html_tag_content_area, NULL},\n {\"article\", B8(01110),'R', Html_tag_open_sectioning, NULL, NULL},\n {\"aside\", B8(01110),'R', Html_tag_open_sectioning, NULL, NULL},\n {\"audio\", B8(01111),'R', Html_tag_open_audio, NULL, Html_tag_close_media},\n {\"b\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"base\", B8(10001),'F', Html_tag_open_base, NULL, NULL},\n /* basefont 010001 -- obsolete in HTML5 */\n /* bdo 010101 */\n {\"big\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"blockquote\", B8(01110),'R', Html_tag_open_blockquote, NULL, NULL},\n {\"body\", B8(01110),'O', Html_tag_open_body, NULL, Html_tag_close_body},\n {\"br\", B8(01001),'F', Html_tag_open_default, Html_tag_content_br, NULL},\n {\"button\", B8(01111),'R', Html_tag_open_button,NULL,Html_tag_close_button},\n /* caption */\n {\"center\", B8(01110),'R', Html_tag_open_default, NULL, NULL},\n {\"cite\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"code\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n /* col 010010 'F' */\n /* colgroup */\n {\"dd\", B8(01110),'O', Html_tag_open_dd, NULL, NULL},\n {\"del\", B8(01111),'R', Html_tag_open_default, NULL, NULL},\n {\"dfn\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"dir\", B8(01100),'R', Html_tag_open_dir, NULL, Html_tag_close_par},\n /* TODO: complete <div> support! */\n {\"div\", B8(01110),'R', Html_tag_open_div, NULL, NULL},\n {\"dl\", B8(01100),'R', Html_tag_open_dl, NULL, Html_tag_close_par},\n {\"dt\", B8(01010),'O', Html_tag_open_dt, NULL, Html_tag_close_par},\n {\"em\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"embed\", B8(01001),'F', Html_tag_open_embed, Html_tag_content_embed, NULL},\n /* fieldset */\n {\"figcaption\", B8(01110),'R', Html_tag_open_default, NULL, NULL},\n {\"figure\", B8(01110),'R', Html_tag_open_default, NULL, NULL},\n {\"font\", B8(01011),'R', Html_tag_open_font, NULL, NULL},\n {\"footer\", B8(01110),'R', Html_tag_open_sectioning, NULL, NULL},\n {\"form\", B8(01110),'R', Html_tag_open_form, NULL, Html_tag_close_form},\n {\"frame\", B8(01000),'F', Html_tag_open_frame, Html_tag_content_frame, NULL},\n {\"frameset\", B8(01110),'R', Html_tag_open_default, Html_tag_content_frameset,\n                             NULL},\n {\"h1\", B8(01010),'R', Html_tag_open_h, NULL, NULL},\n {\"h2\", B8(01010),'R', Html_tag_open_h, NULL, NULL},\n {\"h3\", B8(01010),'R', Html_tag_open_h, NULL, NULL},\n {\"h4\", B8(01010),'R', Html_tag_open_h, NULL, NULL},\n {\"h5\", B8(01010),'R', Html_tag_open_h, NULL, NULL},\n {\"h6\", B8(01010),'R', Html_tag_open_h, NULL, NULL},\n {\"head\", B8(10111),'O', Html_tag_open_head, NULL, Html_tag_close_head},\n {\"header\", B8(01110),'R', Html_tag_open_sectioning, NULL, NULL},\n {\"hr\", B8(01000),'F', Html_tag_open_hr, Html_tag_content_hr, NULL},\n {\"html\", B8(00110),'O', Html_tag_open_html, NULL, Html_tag_close_html},\n {\"i\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"iframe\", B8(01111),'R', Html_tag_open_frame, Html_tag_content_frame, NULL},\n {\"img\", B8(01001),'F', Html_tag_open_img, Html_tag_content_img, NULL},\n {\"input\", B8(01001),'F', Html_tag_open_input, NULL, NULL},\n {\"ins\", B8(01111),'R', Html_tag_open_default, NULL, NULL},\n {\"isindex\", B8(11001),'F', Html_tag_open_isindex, NULL, NULL},\n {\"kbd\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n /* label 010101 */\n /* legend 01?? */\n {\"li\", B8(01110),'O', Html_tag_open_li, NULL, Html_tag_close_li},\n {\"link\", B8(10001),'F', Html_tag_open_link, NULL, NULL},\n {\"main\", B8(01110),'R', Html_tag_open_sectioning, NULL, NULL},\n {\"map\", B8(01101),'R', Html_tag_open_default, Html_tag_content_map,\n                        Html_tag_close_map},\n {\"mark\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n /* menu 1010 -- TODO: not exactly 1010, it can contain LI and inline */\n {\"menu\", B8(01100),'R', Html_tag_open_menu, NULL, Html_tag_close_par},\n {\"meta\", B8(11001),'F', Html_tag_open_meta, NULL, NULL},\n {\"nav\", B8(01110),'R', Html_tag_open_sectioning, NULL, NULL},\n /* noframes 1011 -- obsolete in HTML5 */\n /* noscript 1011 */\n {\"object\", B8(11111),'R', Html_tag_open_object, Html_tag_content_object,NULL},\n {\"ol\", B8(01100),'R', Html_tag_open_ol, NULL, NULL},\n {\"optgroup\", B8(01011),'O', Html_tag_open_optgroup, NULL,\n                             Html_tag_close_optgroup},\n {\"option\", B8(01001),'O', Html_tag_open_option, NULL, Html_tag_close_option},\n {\"p\", B8(01010),'O', Html_tag_open_p, NULL, NULL},\n /* param 010001 'F' */\n {\"pre\", B8(01010),'R', Html_tag_open_pre, NULL, Html_tag_close_pre},\n {\"q\", B8(01011),'R', Html_tag_open_q, Html_tag_content_q, Html_tag_close_q},\n {\"s\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"samp\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"script\", B8(11101),'R', Html_tag_open_script,NULL,Html_tag_close_script},\n {\"section\", B8(01110),'R', Html_tag_open_sectioning, NULL, NULL},\n {\"select\", B8(01011),'R', Html_tag_open_select,NULL,Html_tag_close_select},\n {\"small\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"source\", B8(01001),'F', Html_tag_open_source, Html_tag_content_source,NULL},\n {\"span\", B8(01011),'R', Html_tag_open_span, NULL, NULL},\n {\"strike\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"strong\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"style\", B8(10011),'R', Html_tag_open_style, NULL, Html_tag_close_style},\n {\"sub\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"sup\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"table\", B8(01100),'R', Html_tag_open_table, Html_tag_content_table, NULL},\n {\"tbody\", B8(01010),'O', Html_tag_open_tbody, Html_tag_content_tbody, NULL},\n {\"td\", B8(01110),'O', Html_tag_open_td, Html_tag_content_td, NULL},\n {\"textarea\", B8(01011),'R', Html_tag_open_textarea, Html_tag_content_textarea,\n                             Html_tag_close_textarea},\n {\"tfoot\", B8(01010),'O', Html_tag_open_tfoot, NULL, NULL},\n {\"th\", B8(01110),'O', Html_tag_open_th, Html_tag_content_th, NULL},\n {\"thead\", B8(01010),'O', Html_tag_open_thead, NULL, NULL},\n {\"title\", B8(10011),'R', Html_tag_open_title, NULL, Html_tag_close_title},\n {\"tr\", B8(01100),'O', Html_tag_open_tr, Html_tag_content_tr, NULL},\n {\"tt\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"u\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"ul\", B8(01100),'R', Html_tag_open_ul, NULL, NULL},\n {\"var\", B8(01011),'R', Html_tag_open_default, NULL, NULL},\n {\"video\", B8(01111),'R', Html_tag_open_video, NULL, Html_tag_close_media},\n {\"wbr\", B8(01011),'F', Html_tag_open_default, Html_tag_content_wbr, NULL}\n};\n#define NTAGS (sizeof(Tags)/sizeof(Tags[0]))\n\n\n/*\n * Compares tag from buffer ('/' or '>' or space-ended string) [p1]\n * with tag from taglist (lowercase, zero ended string) [p2]\n * Return value: as strcmp()\n */\nstatic int Html_tag_compare(const char *p1, const char *p2)\n{\n   while ( *p2 ) {\n      if (D_ASCII_TOLOWER(*p1) != *p2)\n         return(D_ASCII_TOLOWER(*p1) - *p2);\n      ++p1;\n      ++p2;\n   }\n   return !strchr(\" >/\\n\\r\\t\", *p1);\n}\n\n/*\n * Get 'tag' index\n * return -1 if tag is not handled yet\n */\nint a_Html_tag_index(const char *tag)\n{\n   int low, high, mid, cond;\n\n   /* Binary search */\n   low = 0;\n   high = NTAGS - 1;          /* Last tag index */\n   while (low <= high) {\n      mid = (low + high) / 2;\n      if ((cond = Html_tag_compare(tag, Tags[mid].name)) < 0 )\n         high = mid - 1;\n      else if (cond > 0)\n         low = mid + 1;\n      else\n         return mid;\n   }\n   return -1;\n}\n\n/*\n * For elements with optional close, check whether is time to close,\n * by also following Firefox's de facto rules.\n * Called at open time.\n *\n * Return value: (1: Close, 0: Don't close)\n * --tuned for speed.\n */\nstatic int Html_triggers_optional_close(int old_idx, int cur_idx)\n{\n   int Flags = Tags[cur_idx].Flags;\n   if (old_idx == i_P || old_idx == i_DT) {\n      /* P and DT are closed by block elements (i.e. non inline)*/\n      return (!(Flags & 1));\n   } else if (old_idx == i_LI) {\n      /* LI closes LI\n       * Note: non-flow should also close it, but FF does not. */\n      return (cur_idx == i_LI);\n   } else if (old_idx == i_TD || old_idx == i_TH) {\n      /* TD and TH are closed by: TD, TH and TR.\n       * Note: non-flow should also close it, but FF does not. */\n      return (cur_idx == i_TD || cur_idx == i_TH || cur_idx == i_TR);\n   } else if (old_idx == i_TR) {\n      /* TR closes TR */\n      return (cur_idx == i_TR);\n   } else if (old_idx == i_DD) {\n      /* DD is closed by DD and DT */\n      return (cur_idx == i_DD || cur_idx == i_DT);\n   } else if (old_idx == i_OPTGROUP) {\n      /* i_OPTGROUP can only contain OPTION */\n      return (cur_idx != i_OPTION);\n   } else if (old_idx == i_OPTION) {\n      return 1;  // OPTION always needs close\n   }\n\n   /* Don't close HTML, HEAD and BODY. They're handled by Html_test_section().\n    * TODO: TBODY is pending */\n   return 0;\n}\n\n/*\n * Check nesting and cross-nesting between BUTTON, SELECT, TEXTAREA and A.\n * The cleanup process will close any of them before opening another.\n * This is not an HTML SPEC restriction , but it avoids lots of trouble\n * inside dillo (concurrent inputs), and makes almost no sense to have.\n * return: index of the open element, -1 if none.\n */\nstatic inline int Html_forbids_cross_nesting(const int InFlags,\n                                             const int new_idx)\n{\n   int f = InFlags, ni = new_idx, oi = -1;\n   if (f & (IN_A | IN_BUTTON | IN_SELECT | IN_TEXTAREA) &&\n       (ni == i_A || ni == i_BUTTON || ni == i_SELECT || ni == i_TEXTAREA))\n      oi = (f & IN_A ? i_A : f & IN_BUTTON ? i_BUTTON : f & IN_SELECT ?\n            i_SELECT : f & IN_TEXTAREA ? i_TEXTAREA : 0);\n   return oi;\n}\n\n/*\n * Cleanup the stack to a given index.\n *\n * 's_idx' stack index to clean up to.\n * 'new_idx' is the tag index that triggered the cleanup.\n * 'fi' forbidden tag index. -1 if allowed (most of the time).\n * 'op' cleanup operation. {'o' =  open, 'c' = close}.\n */\nstatic void Html_tag_cleanup_to_idx(DilloHtml *html, int s_idx,\n                                    int new_idx, int fi, char op)\n{\n   int s_top, ni = new_idx;\n   while ((s_top = html->stack->size() - 1) >= s_idx) {\n      int toptag_idx = S_TOP(html)->tag_idx;\n      TagInfo toptag = Tags[toptag_idx];\n\n      if (fi >= 0) {\n         // forbidden nesting\n         if (toptag_idx != fi)\n            BUG_MSG(\"   Nesting cleanup - forcing close of open tag: <%s>.\",\n                    toptag.name);\n      } else if (s_top == s_idx && op == 'c') {\n         // target tag, no bug when closing.\n      } else if (toptag.EndTag == 'O') {\n         // optional close, that's OK\n      } else if ((!(toptag.Flags & 4) &&\n                  (Tags[ni].Flags & 4 || !(Tags[ni].Flags & 1))) ||\n                 (Tags[ni].Flags & 1 && !(toptag.Flags & 2))) {\n         // block {element, container} in non block container or\n         // inline element in non inline container\n         BUG_MSG((op == 'o') ?\n            \"Bad nesting:  <%s> can't contain <%s>.  -- closing <%s>.\" :\n            \"<%s> needs to be closed before </%s>.  -- closing <%s>.\",\n            toptag.name, Tags[ni].name, toptag.name);\n      } else {\n         BUG_MSG(\n            \"<%s> should have been closed before </%s>.  -- closing <%s>.\",\n            toptag.name, Tags[ni].name, toptag.name);\n      }\n      _MSG(\"op(%c): %s s_top=%d s_idx=%d\\n\", op, toptag.name, s_top, s_idx);\n      if (toptag_idx == i_BODY &&\n          !((html->InFlags & IN_EOF) || html->ReqTagClose)) {\n         (s_idx == 1 ? html->PrevWasHtmlClose : html->PrevWasBodyClose) = true;\n         break; // only pop {BODY,HTML} upon EOF or redundancy\n      }\n      if (toptag.close)\n         toptag.close(html);\n      Html_real_pop_tag(html);\n   }\n}\n\n/*\n * Conditional cleanup of the stack (at open time). Handles:\n *  - Forbidden cross nesting (a BUG).\n *  - Block elements inside non block containers (a BUG).\n *  - Elements with \"optional\" close tag (OK).\n *\n * This function is called before opening/pushing a new tag into the stack.\n * 'ni' is the new tag's index in Tags[].\n */\nstatic void Html_stack_cleanup_at_open(DilloHtml *html, int ni)\n{\n   if (!html->TagSoup)\n      return;\n\n   int s_top = html->stack->size() - 1, s_idx;\n   int fi = Html_forbids_cross_nesting(html->InFlags, ni);\n   for (s_idx = s_top;  s_idx > 0;  --s_idx) {\n      int ti = html->stack->getRef(s_idx)->tag_idx;\n\n      if (fi >= 0) {\n         // forbidden cross nesting found\n         if (ti != fi)\n            continue; // don't allow, close\n         --s_idx;\n         BUG_MSG(\"Forbidden nesting: <%s> can't contain <%s>.  -- closing \"\n                 \"<%s>.\", Tags[fi].name, Tags[ni].name, Tags[fi].name);\n\n      } else if ((html->InFlags & IN_PRE) && ni == i_HR) {\n         break;   // allow Apache's bad HTML directory listings...\n\n      } else if (Tags[ti].EndTag == 'O') {    // Element with optional close\n         if (Html_triggers_optional_close(ti, ni))\n            continue;   // close\n      } else if (!(Tags[ni].Flags & 1) && !(Tags[ti].Flags & 4)) {\n         // Block element over a NON block container\n         if (ti == i_A && html->DocTypeVersion >= 5.0f)\n            break;\n         continue;   // close\n      }\n\n      break;\n   }\n\n   if (s_idx < s_top)\n      Html_tag_cleanup_to_idx(html, s_idx + 1, ni, fi, 'o');\n}\n\n/*\n * Conditional cleanup of the stack, called before closing any tag.\n *\n * There are several ways of doing it. Considering the HTML 4.01 spec\n * which defines optional close tags, and the will to deliver useful diagnose\n * messages for bad-formed HTML, it'll go as follows:\n *\n *   1.- Search the stack for a matching tag by closing elements that:\n *       have optional close | are inline in a block container | force closing.\n *   2.- If it exists, clean all the tags in between.\n *   3.- Cleanup the matching tag. (on error, give a warning message)\n */\nstatic void Html_tag_cleanup_at_close(DilloHtml *html, int new_idx)\n{\n   int stack_idx, tag_idx, matched = 0, expected = 0;\n   TagInfo new_tag = Tags[new_idx];\n\n   /* Look for the candidate tag to close */\n   stack_idx = html->stack->size();\n   while (--stack_idx) {\n      tag_idx = html->stack->getRef(stack_idx)->tag_idx;\n      if (tag_idx == new_idx) {\n         /* matching tag found */\n         matched = 1;\n         break;\n      } else if (Tags[tag_idx].EndTag == 'O') {\n         /* close elements with optional close */\n         continue;\n      } else if ((new_idx == i_A && html->InFlags & IN_A) ||\n                 (new_idx == i_BUTTON && html->InFlags & IN_BUTTON) ||\n                 (new_idx == i_SELECT && html->InFlags & IN_SELECT) ||\n                 (new_idx == i_TEXTAREA && html->InFlags & IN_TEXTAREA)) {\n         /* Let these elements close anything left open inside them */\n         continue;\n      } else if (Tags[new_idx].Flags & 4 &&   // Block container\n                 Tags[stack_idx].Flags & 3) { // Inline element or container\n         /* Let a block container close inline elements left open inside it. */\n         continue;\n      } else {\n         /* this is the tag that should have been closed */\n         expected = 1;\n         break;\n      }\n   }\n\n   if (matched) {\n      Html_tag_cleanup_to_idx(html, stack_idx, new_idx, -1, 'c');\n   } else if (expected) {\n      BUG_MSG(\"Unexpected closing tag: </%s> -- expected </%s>.\",\n              new_tag.name, Tags[tag_idx].name);\n   } else {\n      BUG_MSG(\"Unexpected closing tag: </%s>.\", new_tag.name);\n   }\n}\n\n/*\n * HTML, HEAD and BODY elements have optional open and close tags.\n * Handle this \"magic\" here.\n */\nstatic void Html_test_section(DilloHtml *html, int new_idx, int IsCloseTag)\n{\n   const char *tag;\n   int tag_idx;\n\n   if (!(html->InFlags & IN_HTML) && html->DocType == DT_NONE &&\n       strncmp(html->content_type, \"text/gemini\", 11) &&\n       strncmp(html->content_type, \"text/gopher\", 11) &&\n       strncmp(html->content_type, \"text/markdown\", 13))\n      BUG_MSG(\"The required DOCTYPE declaration is missing. \"\n              \"Handling as HTML4.\");\n\n   if (!(html->InFlags & IN_HTML)) {\n      tag = \"<html>\";\n      tag_idx = a_Html_tag_index(tag + 1);\n      if (tag_idx != new_idx || IsCloseTag) {\n         /* implicit open */\n         Html_force_push_tag(html, tag_idx);\n         _MSG(\"Open : %*s%s\\n\", html->stack->size(),\" \",Tags[tag_idx].name);\n         Tags[tag_idx].open (html, tag, strlen(tag));\n      }\n   }\n\n   if (Tags[new_idx].Flags & 16) {\n      /* head element */\n      if (!(html->InFlags & IN_HEAD) && html->Num_HEAD == 0) {\n         tag = \"<head>\";\n         tag_idx = a_Html_tag_index(tag + 1);\n         if (tag_idx != new_idx || IsCloseTag) {\n            /* implicit open of the head element */\n            Html_force_push_tag(html, tag_idx);\n            _MSG(\"Open : %*s%s\\n\", html->stack->size(),\" \",Tags[tag_idx].name);\n            Tags[tag_idx].open (html, tag, strlen(tag));\n         }\n      }\n\n   } else if (Tags[new_idx].Flags & 8) {\n      /* body element */\n      if (html->InFlags & IN_HEAD) {\n         tag = \"</head>\";\n         tag_idx = a_Html_tag_index(tag + 2);\n         Html_tag_cleanup_at_close(html, tag_idx);\n      }\n      tag = \"<body>\";\n      tag_idx = a_Html_tag_index(tag + 1);\n      if (tag_idx != new_idx || IsCloseTag) {\n         /* implicit open */\n         Html_force_push_tag(html, tag_idx);\n         _MSG(\"Open : %*s%s\\n\", html->stack->size(),\" \",Tags[tag_idx].name);\n         Tags[tag_idx].open (html, tag, strlen(tag));\n      }\n   }\n}\n\n/*\n * Parse attributes that can appear on any tag.\n */\nstatic void Html_parse_common_attrs(DilloHtml *html, char *tag, int tagsize)\n{\n   const char *attrbuf;\n   char lang[3];\n\n   if (tagsize >= 8 &&        /* length of \"<t id=i>\" */\n       (attrbuf = a_Html_get_attr(html, tag, tagsize, \"id\"))) {\n      /* According to the SGML declaration of HTML 4, all NAME values\n       * occuring outside entities must be converted to uppercase\n       * (this is what \"NAMECASE GENERAL YES\" says). But the HTML 4\n       * spec states in Sec. 7.5.2 that anchor ids are case-sensitive.\n       * So we don't do it and hope for better specs in the future ...\n       */\n      Html_check_name_val(html, attrbuf, \"id\");\n\n      html->styleEngine->setId(attrbuf);\n   }\n\n   if (tagsize >= 11 && (prefs.parse_embedded_css || prefs.load_stylesheets)) {\n      /* length of \"<t class=i>\" or \"<t style=i>\" */\n      attrbuf = a_Html_get_attr(html, tag, tagsize, \"class\");\n      if (attrbuf)\n         html->styleEngine->setClass (attrbuf);\n\n      attrbuf = a_Html_get_attr(html, tag, tagsize, \"style\");\n      if (attrbuf)\n         html->styleEngine->setStyle (attrbuf);\n   }\n\n   /* handle \"xml:lang\" and \"lang\" attributes\n    * We use only the first two chars of the value to deal with\n    * extended language tags (see http://www.rfc-editor.org/rfc/bcp/bcp47.txt)\n    */\n   memset(lang, 0, sizeof(lang));\n   if (tagsize >= 14) {\n      /* length of \"<t xml:lang=i>\" */\n      attrbuf = a_Html_get_attr(html, tag, tagsize, \"xml:lang\");\n      if (attrbuf)\n         strncpy(lang, attrbuf, 2);\n   }\n   if (!lang[0] && tagsize >= 10) { /* 'xml:lang' prevails over 'lang' */\n      /* length of \"<t lang=i>\" */\n      attrbuf = a_Html_get_attr(html, tag, tagsize, \"lang\");\n      if (attrbuf)\n         strncpy(lang, attrbuf, 2);\n   }\n   if (lang[0])\n      html->styleEngine->setNonCssHint(PROPERTY_X_LANG, CSS_TYPE_STRING, lang);\n}\n\n/*\n * Warn when encountering elements that are obsolete in HTML5. This list\n * was from the \"W3C Candidate Recommendation 6 August 2013\".\n */\nstatic void Html_check_html5_obsolete(DilloHtml *html, int ni)\n{\n   static int indexes[9] = {-1};\n\n   if (indexes[0] == -1) {\n      indexes[0] = a_Html_tag_index(\"dir\");\n      indexes[1] = a_Html_tag_index(\"frame\");\n      indexes[2] = a_Html_tag_index(\"frameset\");\n      indexes[3] = a_Html_tag_index(\"isindex\");\n      indexes[4] = a_Html_tag_index(\"strike\");\n      indexes[5] = a_Html_tag_index(\"big\");\n      indexes[6] = a_Html_tag_index(\"center\");\n      indexes[7] = a_Html_tag_index(\"font\");\n      indexes[8] = a_Html_tag_index(\"tt\");\n   }\n   for (int i = 0; i < 9; i++) {\n      if (indexes[i] == ni) {\n         BUG_MSG(\"<%s> is obsolete in HTML5.\", Tags[ni].name);\n         break;\n      }\n   }\n}\n\nstatic void Html_display_block(DilloHtml *html)\n{\n   Html_add_textblock(html, Html_must_add_breaks (html), 0,\n                      false /* Perhaps true for widgets oof? */);\n}\n\nstatic void Html_display_inline_block(DilloHtml *html)\n{\n   Html_add_textblock(html, false, 0, true);\n}\n\nstatic void Html_display_listitem(DilloHtml *html)\n{\n   Style *style = html->style ();\n   Style *wordStyle = html->wordStyle ();\n   Widget **ref_list_item;\n   ListItem *list_item;\n   int *list_number;\n   char buf[16];\n\n   /* Get our parent tag's variables (used as state storage) */\n   list_number = &html->stack->getRef(html->stack->size()-2)->list_number;\n   ref_list_item = &html->stack->getRef(html->stack->size()-2)->ref_list_item;\n\n   HT2TB(html)->addParbreak (0, wordStyle);\n\n   list_item = new ListItem ((ListItem*)*ref_list_item,prefs.limit_text_width);\n   HT2TB(html)->addWidget (list_item, style);\n   HT2TB(html)->addParbreak (0, wordStyle);\n   *ref_list_item = list_item;\n   S_TOP(html)->textblock = html->dw = list_item;\n\n   if (style->listStyleType == LIST_STYLE_TYPE_NONE) {\n      // none\n   } else if (style->listStyleType >= LIST_STYLE_TYPE_DECIMAL) {\n      // ordered\n      numtostr((*list_number)++, buf, 16, style->listStyleType);\n      list_item->initWithText (buf, wordStyle);\n   } else {\n      // unordered\n      list_item->initWithWidget (new Bullet(), wordStyle);\n   }\n}\n\n/*\n * Process a HTML tag, given as 'tag' and 'tagsize'. -- tagsize is [1 based]\n * ('tag' must include the enclosing angle brackets)\n * This function calls the right open or close function for the tag.\n */\nstatic void Html_process_tag(DilloHtml *html, char *tag, int tagsize)\n{\n   int ti, ni;           /* stack tag index and new tag index */\n   char *start = tag + 1; /* discard the '<' */\n   int IsCloseTag = (*start == '/');\n\n   dReturn_if (html->stop_parser == true);\n\n   ni = a_Html_tag_index(start + IsCloseTag);\n   if (ni == -1) {\n      /* TODO: doctype parsing is a bit fuzzy, but enough for the time being */\n      if (!(html->InFlags & IN_HTML)) {\n         if (tagsize > 9 && !dStrnAsciiCasecmp(tag, \"<!doctype\", 9))\n            Html_parse_doctype(html, tag, tagsize);\n      }\n      /* Ignore unknown tags */\n      return;\n   }\n   _MSG(\"Html_process_tag: %s%s\\n\", IsCloseTag ? \"/\" : \"\", Tags[ni].name);\n\n   if (!IsCloseTag && html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n      Html_check_html5_obsolete(html, ni);\n\n   int i = html->PrevWasHtmlClose ? 1 : html->PrevWasBodyClose ? 2 : 0;\n   if (i == 1 || (i == 2 && ni != i_HTML))\n      BUG_MSG(\"Content after </%s> tag.\", i == 1 ? \"html\" : \"body\");\n   html->PrevWasHtmlClose = html->PrevWasBodyClose = false;\n\n   /* Handle HTML, HEAD and BODY. Elements with optional open and close */\n   if (!(html->InFlags & IN_BODY) /* && parsing HTML */)\n      Html_test_section(html, ni, IsCloseTag);\n\n   /* Tag processing */\n   ti = S_TOP(html)->tag_idx;\n   switch (IsCloseTag) {\n   case 0:\n      /* Open function */\n\n      /* Cleanup before opening a new tag */\n      if (ti != -1)\n         Html_stack_cleanup_at_open(html, ni);\n\n      /* TODO: this is only raising a warning, but allows the element.\n       * Note: Apache uses IMG inside PRE. */\n      if ((html->InFlags & IN_PRE) && Html_tag_pre_excludes(html, ni))\n         BUG_MSG(\"<pre> is not allowed to contain <%s>.\", Tags[ni].name);\n\n      /* Push the tag into the stack */\n      Html_push_tag(html, ni);\n\n      html->startElement (ni);\n      _MSG(\"Open : %*s%s\\n\", html->stack->size(), \" \", Tags[ni].name);\n\n      /* Parse attributes that can appear on any tag */\n      Html_parse_common_attrs(html, tag, tagsize);\n\n      /* Call the open function for this tag */\n      _MSG(\"Html_process_tag Open : %s\\n\", Tags[ni].name);\n      Tags[ni].open (html, tag, tagsize);\n\n      if (! S_TOP(html)->display_none) {\n         switch (html->style ()->display) {\n            case DISPLAY_BLOCK:\n               Html_display_block(html);\n               break;\n            case DISPLAY_INLINE_BLOCK:\n               Html_display_inline_block(html);\n               break;\n            case DISPLAY_LIST_ITEM:\n               Html_display_listitem(html);\n               break;\n            case DISPLAY_NONE:\n               S_TOP(html)->display_none = true;\n               break;\n            case DISPLAY_INLINE:\n               if (html->style()->vloat != FLOAT_NONE)\n                  Html_display_block(html);\n               break;\n            default:\n               break;\n         }\n\n         if (Tags[ni].content && ! S_TOP(html)->display_none) {\n            Tags[ni].content (html, tag, tagsize);\n         }\n      }\n\n      if (html->stop_parser)\n         break;\n\n      if (S_TOP(html)->parse_mode == DILLO_HTML_PARSE_MODE_VERBATIM) {\n         /* don't change anything */\n      } else if (S_TOP(html)->parse_mode != DILLO_HTML_PARSE_MODE_PRE &&\n          (html->style ()->whiteSpace == WHITE_SPACE_PRE ||\n           html->style ()->whiteSpace == WHITE_SPACE_PRE_WRAP)) {\n         S_TOP(html)->parse_mode = DILLO_HTML_PARSE_MODE_PRE;\n         html->pre_column = 0;\n         html->PreFirstChar = true;\n      }\n\n      if (html->styleEngine->getId ())\n         Html_add_anchor(html, html->styleEngine->getId ());\n\n      /* Request immediate close for elements with forbidden close tag. */\n      /* TODO: XHTML always requires close tags. A simple implementation\n       * of the commented clause below will make it work. */\n      if (/* parsing HTML && */ Tags[ni].EndTag == 'F')\n         html->ReqTagClose = true;\n\n      /* Don't break! Open tags may also close themselves */\n\n   default:\n      /* Close function */\n\n      /* Test for </x>, ReqTagClose, <x /> and <x/> */\n      if (*start == '/' ||                                      /* </x>    */\n          html->ReqTagClose ||                                  /* request */\n          (tag[tagsize-2] == '/' &&                             /* XML:    */\n           (strchr(\" \\\"'\", tag[tagsize-3]) ||                   /* [ \"']/> */\n            (size_t)tagsize == strlen(Tags[ni].name) + 3))) {   /*  <x/>   */\n\n         _MSG(\"Html_process_tag Close: %s\\n\", Tags[ni].name);\n         Html_tag_cleanup_at_close(html, ni);\n         /* This was a close tag */\n         html->ReqTagClose = false;\n      }\n   }\n}\n\n/*\n * Process an RSS tag, converting it to an HTML equivalent to be displayed.\n */\nstatic void Rss_process_tag(DilloHtml *html, char *tag, int tagsize)\n{\n   char channel_tag[]=\"<div class=\\\"channel\\\">\", channel_tag_end[]=\"</div>\";\n   char link_tag[]=\"<a>\", link_tag_end[]=\"</a>\";\n   char title_tag[]=\"<h1>\", title_tag_end[]=\"</h1>\";\n   char description_tag[]=\"<div class=\\\"description\\\">\", description_tag_end[]=\"</div>\";\n   char item_tag[]=\"<div class=\\\"item\\\">\", item_tag_end[]=\"</div>\";\n   char pubdate_tag[]=\"<div class=\\\"pubdate\\\">\", pubdate_tag_end[]=\"</div>\";\n\n   char doctype_tag[]=\"<!DOCTYPE html>\";\n   char head_tag[]=\"<head>\", head_tag_end[]=\"</head>\";\n   char head_title_tag[]=\"<title>\", head_title_tag_end[]=\"</title>\";\n   char hr_tag[]=\"<hr />\";\n   char style_tag[]=\"<style type=\\\"text/css\\\">\", style_tag_end[]=\"</style>\";\n   char style[]=\".channel { margin-bottom: 10px; } .channel > h1 { font-size: bigger; } .pubdate { font-style: italic; } .description { margin-top: 10px; } \";\n\n   // rss headers\n   if(tagsize > 5 && !strncmp(tag, \"<?xml\", 5)) {\n      return;\n   } else if(tagsize > 4 && !strncmp(tag, \"<rss\", 4)) {\n      Html_process_tag(html, doctype_tag, sizeof(doctype_tag)-1);\n      Html_process_tag(html, head_tag, sizeof(head_tag)-1);\n      if(a_Url_hostname(html->page_url)) {\n         Html_process_tag(html, head_title_tag, sizeof(head_title_tag)-1);\n         Html_process_word(html, a_Url_hostname(html->page_url), strlen(a_Url_hostname(html->page_url)));           Html_process_tag(html, head_title_tag_end, sizeof(head_title_tag_end)-1);\n      }\n      Html_process_tag(html, style_tag, sizeof(style_tag)-1);\n      Html_process_word(html, style, sizeof(style)-1);\n      Html_process_tag(html, style_tag_end, sizeof(style_tag_end)-1);\n      Html_process_tag(html, head_tag_end, sizeof(head_tag_end)-1);\n      return;\n   }\n\n   // channel\n   else if(tagsize > 8 && !strncmp(tag, \"<channel\", 8)) {\n      return Html_process_tag(html, channel_tag, sizeof(channel_tag)-1);\n   } else if(tagsize > 9 && !strncmp(tag, \"</channel\", 9)) {\n      return Html_process_tag(html, channel_tag_end, sizeof(channel_tag_end)-1);\n   }\n   \n   // link\n   else if(tagsize > 5 && !strncmp(tag, \"<link\", 5)) {\n      return Html_process_tag(html, link_tag, sizeof(link_tag)-1);\n   } else if(tagsize > 6 && !strncmp(tag, \"</link\", 6)) {\n      return Html_process_tag(html, link_tag_end, sizeof(link_tag_end)-1);\n   }\n\n   // url\n   else if(tagsize > 4 && !strncmp(tag, \"<url\", 4)) {\n      return Html_process_tag(html, link_tag, sizeof(link_tag)-1);\n   } else if(tagsize > 5 && !strncmp(tag, \"</url\", 5)) {\n      return Html_process_tag(html, link_tag_end, sizeof(link_tag_end)-1);\n   }\n   \n   // title\n   else if(tagsize > 6 && !strncmp(tag, \"<title\", 6)) {\n      return Html_process_tag(html, title_tag, sizeof(title_tag)-1);\n   } else if(tagsize > 7 && !strncmp(tag, \"</title\", 7)) {\n      return Html_process_tag(html, title_tag_end, sizeof(title_tag_end)-1);\n   }\n   \n   // description\n   else if(tagsize > 12 && !strncmp(tag, \"<description\", 12)) {\n      return Html_process_tag(html, description_tag, sizeof(description_tag)-1);\n   } else if(tagsize > 13 && !strncmp(tag, \"</description\", 13)) {\n      return Html_process_tag(html, description_tag_end, sizeof(description_tag_end)-1);\n   }\n   \n   // item\n   else if(tagsize > 5 && !strncmp(tag, \"<item\", 5)) {\n      Html_process_tag(html, hr_tag, sizeof(hr_tag)-1);\n      return Html_process_tag(html, item_tag, sizeof(item_tag)-1);\n   } else if(tagsize > 6 && !strncmp(tag, \"</item\", 6)) {\n      return Html_process_tag(html, item_tag_end, sizeof(item_tag_end)-1);\n   }\n   \n   // pubDate\n   else if(tagsize > 8 && !strncmp(tag, \"<pubDate\", 8)) {\n      return Html_process_tag(html, pubdate_tag, sizeof(pubdate_tag)-1);\n   } else if(tagsize > 9 && !strncmp(tag, \"</pubDate\", 9)) {\n      return Html_process_tag(html, pubdate_tag_end, sizeof(pubdate_tag_end)-1);\n   }\n      \n   // normal html tag\n   else {\n      return Html_process_tag(html, tag, tagsize);\n   }\n}\n\n/*\n * Get attribute value for 'attrname' and return it.\n *  Tags start with '<' and end with a '>' (Ex: \"<P align=center>\")\n *  tagsize = strlen(tag) from '<' to '>', inclusive.\n *\n * Returns one of the following:\n *    * The value of the attribute.\n *    * An empty string if the attribute exists but has no value.\n *    * NULL if the attribute doesn't exist.\n */\nstatic const char *Html_get_attr2(DilloHtml *html,\n                                  const char *tag,\n                                  int tagsize,\n                                  const char *attrname,\n                                  int tag_parsing_flags)\n{\n   int i, entsize, Found = 0, delimiter = 0, attr_pos = 0;\n   Dstr *Buf = html->attr_data;\n   DilloHtmlTagParsingState state = SEEK_ATTR_START;\n\n   dReturn_val_if_fail(*attrname, NULL);\n\n   dStr_truncate(Buf, 0);\n\n   for (i = 1; i < tagsize; ++i) {\n      switch (state) {\n      case SEEK_ATTR_START:\n         if (isspace(tag[i]))\n            state = SEEK_TOKEN_START;\n         else if (tag[i] == '=')\n            state = SEEK_VALUE_START;\n         break;\n\n      case MATCH_ATTR_NAME:\n         if (!attrname[attr_pos] &&\n             (tag[i] == '=' || isspace(tag[i]) || tag[i] == '>')) {\n            Found = 1;\n            state = SEEK_TOKEN_START;\n            --i;\n         } else if (!tag[i]) {\n            state = SEEK_ATTR_START; // NULL byte is not allowed\n         } else {\n            if (D_ASCII_TOLOWER(tag[i]) != D_ASCII_TOLOWER(attrname[attr_pos]))\n               state = SEEK_ATTR_START;\n            attr_pos++;\n         }\n         break;\n\n      case SEEK_TOKEN_START:\n         if (tag[i] == '=') {\n            state = SEEK_VALUE_START;\n         } else if (!isspace(tag[i])) {\n            attr_pos = 0;\n            state = (Found) ? FINISHED : MATCH_ATTR_NAME;\n            --i;\n         }\n         break;\n      case SEEK_VALUE_START:\n         if (!isspace(tag[i])) {\n            delimiter = (tag[i] == '\"' || tag[i] == '\\'') ? tag[i] : ' ';\n            i -= (delimiter == ' ');\n            state = (Found) ? GET_VALUE : SKIP_VALUE;\n         }\n         break;\n\n      case SKIP_VALUE:\n         if ((delimiter == ' ' && isspace(tag[i])) || tag[i] == delimiter)\n            state = SEEK_TOKEN_START;\n         break;\n      case GET_VALUE:\n         if ((delimiter == ' ' && (isspace(tag[i]) || tag[i] == '>')) ||\n             tag[i] == delimiter) {\n            state = FINISHED;\n         } else if (tag[i] == '&' &&\n                    (tag_parsing_flags & HTML_ParseEntities)) {\n            const char *entstr;\n            const bool_t is_attr = TRUE;\n\n            if ((entstr = Html_parse_entity(html, tag+i, tagsize-i, &entsize,\n                                            is_attr))) {\n               dStr_append(Buf, entstr);\n               i += entsize-1;\n            } else {\n               dStr_append_c(Buf, tag[i]);\n            }\n         } else if (tag[i] == '\\r' || tag[i] == '\\t') {\n            dStr_append_c(Buf, ' ');\n         } else if (tag[i] == '\\n') {\n            /* ignore */\n         } else {\n            dStr_append_c(Buf, tag[i]);\n         }\n         break;\n\n      case FINISHED:\n         i = tagsize;\n         break;\n      }\n   }\n\n   if (tag_parsing_flags & HTML_LeftTrim)\n      while (isspace(Buf->str[0]))\n         dStr_erase(Buf, 0, 1);\n   if (tag_parsing_flags & HTML_RightTrim)\n      while (Buf->len && isspace(Buf->str[Buf->len - 1]))\n         dStr_truncate(Buf, Buf->len - 1);\n\n   return (Found) ? Buf->str : NULL;\n}\n\n/*\n * Call Html_get_attr2 telling it to parse entities and strip the result\n */\nconst char *a_Html_get_attr(DilloHtml *html,\n                            const char *tag,\n                            int tagsize,\n                            const char *attrname)\n{\n   return Html_get_attr2(html, tag, tagsize, attrname,\n                         HTML_LeftTrim | HTML_RightTrim | HTML_ParseEntities);\n}\n\n/*\n * \"a_Html_get_attr with default\"\n * Call a_Html_get_attr() and dStrdup() the returned string.\n * If the attribute isn't found a copy of 'def' is returned.\n */\nchar *a_Html_get_attr_wdef(DilloHtml *html,\n                           const char *tag,\n                           int tagsize,\n                           const char *attrname,\n                           const char *def)\n{\n   const char *attrbuf = a_Html_get_attr(html, tag, tagsize, attrname);\n\n   return attrbuf ? dStrdup(attrbuf) : dStrdup(def);\n}\n\n/*\n * Dispatch the apropriate function for 'Op'\n * This function is a Cache client and gets called whenever new data arrives\n *  Op      : operation to perform.\n *  CbData  : a pointer to a DilloHtml structure\n *  Buf     : a pointer to new data\n *  BufSize : new data size (in bytes)\n */\nstatic void Html_callback(int Op, CacheClient_t *Client)\n{\n   DilloHtml *html = (DilloHtml*)Client->CbData;\n\n   if (Op) { /* EOF */\n      html->write((char*)Client->Buf, Client->BufSize, 1);\n      html->finishParsing(Client->Key);\n   } else {\n      html->write((char*)Client->Buf, Client->BufSize, 0);\n   }\n}\n\n/*\n * Here's where we parse the html and put it into the Textblock structure.\n * Return value: number of bytes parsed\n */\nstatic int Html_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof,\n                          void process_tag(DilloHtml *html, char *tag, int tagsize))\n{\n   char ch = 0, *p, *text;\n   int token_start, buf_index;\n\n   /* Now, 'buf' and 'bufsize' define a buffer aligned to start at a token\n    * boundary. Iterate through tokens until end of buffer is reached. */\n   buf_index = 0;\n   token_start = buf_index;\n   while ((buf_index < bufsize) && !html->stop_parser) {\n      /* invariant: buf_index == bufsize || token_start == buf_index */\n\n      if (S_TOP(html)->parse_mode ==\n          DILLO_HTML_PARSE_MODE_VERBATIM) {\n         /* Non HTML code here, let's skip until closing tag */\n         do {\n            const char *tag = Tags[S_TOP(html)->tag_idx].name;\n            buf_index += strcspn(buf + buf_index, \"<\");\n            if (buf_index + (int)strlen(tag) + 3 > bufsize) {\n               buf_index = bufsize;\n            } else if (strncmp(buf + buf_index, \"</\", 2) == 0 &&\n                       Html_match_tag(tag, buf+buf_index+2, strlen(tag)+1)) {\n               /* copy VERBATIM text into the stash buffer */\n               text = dStrndup(buf + token_start, buf_index - token_start);\n               dStr_append(html->Stash, text);\n               dFree(text);\n               token_start = buf_index;\n               break;\n            } else\n               ++buf_index;\n         } while (buf_index < bufsize);\n\n         if (buf_index == bufsize)\n            break;\n      }\n\n      if (isspace(buf[buf_index])) {\n         /* whitespace: group all available whitespace */\n         while (++buf_index < bufsize && isspace(buf[buf_index])) ;\n         Html_process_space(html, buf + token_start, buf_index - token_start);\n         token_start = buf_index;\n\n      } else if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) &&\n                 (isalpha(ch) || strchr(\"/!?\", ch)) ) {\n         /* Tag */\n         if (buf_index + 3 < bufsize && !strncmp(buf + buf_index, \"<!--\", 4)) {\n            /* Comment: search for close of comment, skipping over\n             * everything except a matching \"-->\" tag. */\n            while ( (p = (char*) memchr(buf + buf_index, '>',\n                                        bufsize - buf_index)) ){\n               buf_index = p - buf + 1;\n               if (p[-1] == '-' && p[-2] == '-') break;\n            }\n            if (p) {\n               /* Got the whole comment. Let's throw it away! :) */\n               token_start = buf_index;\n            } else\n               buf_index = bufsize;\n         } else if (buf_index + 8 < bufsize && !strncmp(buf + buf_index, \"<![CDATA[\", 9)) {\n            /* CDATA: skip tag opening and try to render the data inside */\n            buf_index += 9;\n            token_start = buf_index;\n         } else {\n            /* Tag: search end of tag (skipping over quoted strings) */\n            html->CurrOfs = html->Start_Ofs + token_start;\n\n            while ( buf_index < bufsize ) {\n               buf_index++;\n               buf_index += strcspn(buf + buf_index, \">\\\"'<\");\n               if ((ch = buf[buf_index]) == '>') {\n                  break;\n               } else if (ch == '\"' || ch == '\\'') {\n                  /* Skip over quoted string */\n                  buf_index++;\n                  buf_index += strcspn(buf + buf_index,\n                                       (ch == '\"') ? \"\\\">\" : \"'>\");\n                  if (buf[buf_index] == '>') {\n                     /* Unterminated string value? Let's look ahead and test:\n                      * (<: unterminated, closing-quote: terminated) */\n                     int offset = buf_index + 1;\n                     offset += strcspn(buf + offset,\n                                       (ch == '\"') ? \"\\\"<\" : \"'<\");\n                     if (buf[offset] == ch || !buf[offset]) {\n                        buf_index = offset;\n                     } else {\n                        BUG_MSG(\"Attribute lacks closing quote.\");\n                        break;\n                     }\n                  }\n               } else if (ch == '<') {\n                  /* unterminated tag detected */\n                  p = dStrndup(buf+token_start+1,\n                               strcspn(buf+token_start+1, \" <\\n\\r\\t\"));\n                  BUG_MSG(\"<%s> lacks its closing '>'.\", p);\n                  dFree(p);\n                  --buf_index;\n                  break;\n               }\n            }\n            if (buf_index < bufsize) {\n               buf_index++;\n               process_tag(html, buf + token_start,\n                           buf_index - token_start);\n               token_start = buf_index;\n            }\n         }\n      } else {\n         /* A Word: search for whitespace or tag open */\n         html->CurrOfs = html->Start_Ofs + token_start;\n\n         while (++buf_index < bufsize) {\n            buf_index += strcspn(buf + buf_index, \" <\\n\\r\\t\\f\\v\");\n            if (buf[buf_index] == '<' && (ch = buf[buf_index + 1]) &&\n                !isalpha(ch) && !strchr(\"/!?\", ch))\n               continue;\n            break;\n         }\n         if (buf_index < bufsize || Eof) {\n            /* successfully found end of token */\n            ch = buf[buf_index];\n            buf[buf_index] = 0;\n            if(buf_index - token_start >= 3 &&\n               !strncmp(buf + token_start, \"]]>\", 3)) {\n               // skip end of <![CDATA[ tag\n            } else {\n               Html_process_word(html, buf + token_start,\n                                 buf_index - token_start);\n            }\n            buf[buf_index] = ch;\n            token_start = buf_index;\n         }\n      }\n   }/*while*/\n\n   HT2TB(html)->flush ();\n\n   return token_start;\n}\n\n/*\n * Some macro utilities for parsing text\n */\n\n#define NEXT1CH(c1) (buf[buf_index] == (c1))\n#define NEXT2CH(c1, c2) (buf_index + 1 < bufsize && buf[buf_index] == (c1) && buf[buf_index+1] == (c2))\n#define NEXT3CH(c1, c2, c3) (buf_index + 2 < bufsize && buf[buf_index] == (c1) && buf[buf_index+1] == (c2) && buf[buf_index+2] == (c3))\n#define NEXT4CH(c1, c2, c3, c4) (buf_index + 3 < bufsize && buf[buf_index] == (c1) && buf[buf_index+1] == (c2) && buf[buf_index+2] == (c3) && buf[buf_index+3] == (c4))\n#define NEXT5CH(c1, c2, c3, c4, c5) (buf_index + 4 < bufsize && buf[buf_index] == (c1) && buf[buf_index+1] == (c2) && buf[buf_index+2] == (c3) && buf[buf_index+3] == (c4) && buf[buf_index+4] == (c5))\n#define NEXT6CH(c1, c2, c3, c4, c5, c6) (buf_index + 5 < bufsize && buf[buf_index] == (c1) && buf[buf_index+1] == (c2) && buf[buf_index+2] == (c3) && buf[buf_index+3] == (c4) && buf[buf_index+4] == (c5) && buf[buf_index+5] == (c6))\n#define NEXT7CH(c1, c2, c3, c4, c5, c6, c7) (buf_index + 6 < bufsize && buf[buf_index] == (c1) && buf[buf_index+1] == (c2) && buf[buf_index+2] == (c3) && buf[buf_index+3] == (c4) && buf[buf_index+4] == (c5) && buf[buf_index+5] == (c6) && buf[buf_index+6] == (c7))\n\n/*\n * Here's where we parse the gemini code and put it into the Textblock structure.\n * Return value: number of bytes parsed\n */\nstatic int Gemini_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof)\n{\n   char ch = 0;\n   int token_start, buf_index;\n\n   char title_open[] = \"<title>\",  title_close[] = \"</title>\";\n   char body_open[] = \"<body>\";\n   char par_open[] = \"<p>\", par_close[] = \"</p>\";\n   char heading_open[16] = \"<hX>\", heading_close[16] = \"</hX>\";\n   char pre_open[] = \"<pre>\", pre_close[] = \"</pre>\";\n   char quote_open[] = \"<blockquote>\", quote_close[] = \"</blockquote>\";\n   char link_open[2048] = \"\", link_close[] = \"</a>\";\n   char list_open[] = \"<ul>\", list_close[] = \"</ul>\";\n   char list_elem_open[] = \"<li>\", list_elem_close[] = \"</li>\";\n   char br_tag[] = \"<br/>\";\n\n   bool in_par, in_heading, in_pre, in_quote, in_link, in_list, in_list_elem, in_middle_line;\n   in_par = in_heading = in_pre = in_quote = in_link = in_list = in_list_elem = in_middle_line = false;\n\n   /* Restore flags */\n   in_pre = html->in_pre;\n\n   /* Set default title and open body tag at the beginning */\n   if (html->CurrOfs == 0) {\n      if(a_Url_hostname(html->page_url)) {\n         Html_process_tag(html, title_open, strlen(title_open));\n         Html_process_word(html, a_Url_hostname(html->page_url), strlen(a_Url_hostname(html->page_url)));\n         Html_process_tag(html, title_close, strlen(title_close));\n      }\n\n      Html_process_tag(html, body_open, strlen(body_open));\n   }\n\n   /* Now, 'buf' and 'bufsize' define a buffer aligned to start at a token\n    * boundary. Iterate through tokens until end of buffer is reached. */\n   buf_index = 0;\n   token_start = buf_index;\n   while ((buf_index < bufsize) && !html->stop_parser) {\n      /* invariant: buf_index == bufsize || token_start == buf_index */\n\n      if(!in_middle_line) {\n         /* we are at the beginning of a line */\n\n         if (in_list && NEXT1CH('*')) {\n            /* close multiline list */\n            Html_process_tag(html, list_close, strlen(list_close));\n            in_list = false;\n         }\n\n         if (NEXT1CH('#')) {\n            /* open heading: determine the heading level (max level = 9) */\n            while (buf_index - token_start < 9 && ++buf_index < bufsize && buf[buf_index] == '#') ;\n            in_heading = true;\n            in_middle_line = true;\n            if(in_par) { /* close par if needed */\n               in_par = false;\n               Html_process_tag(html, par_close, strlen(par_close));\n            }\n            snprintf(heading_open, sizeof(heading_open), \"<h%d>\", buf_index - token_start);\n            snprintf(heading_close, sizeof(heading_close), \"</h%d>\", buf_index - token_start);\n            Html_process_tag(html, heading_open, strlen(heading_open));\n            while (buf_index < bufsize && buf[buf_index] != '\\n' && isspace(buf[buf_index])) buf_index++; /* skip spaces */\n            token_start = buf_index;\n         }\n\n         else if (NEXT3CH('`', '`', '`')) {\n            /* open/close pre */\n            buf_index += 3;\n            if(in_pre) {\n               in_pre = false;\n               Html_process_tag(html, pre_close, strlen(pre_close));\n            } else {\n               in_pre = true;\n               Html_process_tag(html, pre_open, strlen(pre_open));\n            }\n            while (buf_index < bufsize && (buf[buf_index] && buf[buf_index] != '\\n'))\n               buf_index++; /* skip to next line */\n            if (buf_index < bufsize && buf[buf_index] == '\\n') buf_index++;\n            token_start = buf_index;\n         }\n        \n         else if (NEXT1CH('>')) {\n            /* open quoted par */\n            buf_index++;\n            Html_process_tag(html, quote_open, strlen(quote_open));\n            in_quote = true;\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n      \n         else if (NEXT1CH('*')) {\n            /* open list or list element */\n            buf_index ++;\n            if(!in_list) { /* beginning of multiline list */\n               in_list = true;\n               Html_process_tag(html, list_open, strlen(list_open));\n            }\n            in_middle_line = true;\n            in_list_elem = true;\n            Html_process_tag(html, list_elem_open, strlen(list_elem_open)); /* new list element */\n            token_start = buf_index;\n         }\n      \n         else if (NEXT2CH('=', '>')) {\n            /* open link: determine href and text */\n            buf_index += 2;\n            in_link = true;\n            in_middle_line = true;\n            while (buf_index < bufsize && buf[buf_index] != '\\n' && isspace(buf[buf_index])) buf_index++; /* skip spaces */\n            token_start = buf_index;\n            while (buf_index < bufsize && !isspace(buf[buf_index]) && buf[buf_index] != '\\n')\n               buf_index++; /* read href */\n            ch = buf[buf_index]; buf[buf_index] = '\\0'; /* null-terminate temporarily the href */\n            strncpy(link_open, \"<a href=\\\"\", 10);\n            strncat(link_open, buf + token_start, sizeof(link_open) - 12);\n            strncat(link_open, \"\\\">\", 3);\n            buf[buf_index] = ch; /* restore the correct char */\n            Html_process_tag(html, par_open, strlen(par_open));\n            Html_process_tag(html, link_open, strlen(link_open));\n\n            /* check if there is text, otherwise use the link URL as text */\n            while (buf_index < bufsize && isspace(buf[buf_index]) && buf[buf_index] != '\\n')\n               buf_index++;\n            if(buf[buf_index] != '\\n') token_start = buf_index;\n            else buf_index = token_start;\n         }\n\n         else if (NEXT1CH('\\n')) {\n            /* empty line */\n            buf_index++;\n            Html_process_tag(html, br_tag, strlen(br_tag));\n            token_start = buf_index;\n         }\n\n         else {\n            /* no match in this section: continue to generic markup */\n            goto GENERIC_GEMINI_MARKUP;\n         }\n\n         /* a match occurred: continue with main while loop */\n         goto END_OF_CYCLE_GEMINI;\n\n      }\n\n      else {\n         /* we are in the middle of a line */\n\n         if (NEXT1CH('\\n') || NEXT1CH('\\0')) {\n            /* close line (and maybe section) */\n            if (in_par) {\n               Html_process_tag(html, par_close, strlen(par_close));\n               in_par = false;\n            } else if (in_heading) {\n               Html_process_tag(html, heading_close, strlen(heading_close));\n               in_heading = false;\n            } else if (in_pre) {\n               Html_process_word(html, \"\\n\", 1);\n            } else if (in_quote) {\n               Html_process_tag(html, quote_close, strlen(quote_close));\n               in_quote = false;\n            } else if (in_link) {\n               Html_process_tag(html, link_close, strlen(link_close));\n               Html_process_tag(html, par_close, strlen(par_close));\n               in_link = false;\n            } else if (in_list_elem) {\n               Html_process_tag(html, list_elem_close, strlen(list_elem_close));\n               in_list_elem = false;\n            } else {\n               BUG_MSG(\"End of line but not in gemini section.\\n\");\n            }\n            in_middle_line = false;\n            if (buf[buf_index] == '\\n') buf_index++;\n            token_start = buf_index;\n         }\n\n         else {\n            /* no match in this section: continue to generic markup */\n            goto GENERIC_GEMINI_MARKUP;\n         }\n\n         /* a match occurred: continue with main while loop */\n         goto END_OF_CYCLE_GEMINI;\n\n      }\n\n   GENERIC_GEMINI_MARKUP:\n\n      if (isspace(buf[buf_index])) {\n         /* whitespace: group all available whitespace */\n         if (!in_middle_line && !in_par && !in_pre) {\n            /* start new paragraph */\n            in_par = true;\n            Html_process_tag(html, par_open, strlen(par_open));\n         }\n         while (++buf_index < bufsize && isspace(buf[buf_index]) && buf[buf_index] != '\\n') ;\n         Html_process_space(html, buf + token_start, buf_index - token_start);\n         in_middle_line = true;\n         token_start = buf_index;\n      }\n\n      else {\n         /* a word: add to section */\n\n         if (!in_middle_line && !in_par && !in_pre) {\n            /* start new paragraph */\n            in_par = true;\n            in_middle_line = true;\n            Html_process_tag(html, par_open, strlen(par_open));\n         } else if (!in_middle_line && in_pre) {\n            in_middle_line = true;\n         } else if (!in_middle_line) {\n            in_middle_line = true;\n            BUG_MSG(\"Word outside a gemini section\");\n         }\n        \n         buf_index += strcspn(buf + buf_index, \" \\n\");\n\n         if (buf_index < bufsize || Eof) {\n            /* successfully found end of word */\n            ch = buf[buf_index];\n            buf[buf_index] = 0;\n            Html_process_word(html, buf + token_start,\n                              buf_index - token_start);\n            buf[buf_index] = ch;\n            token_start = buf_index;\n         }\n      }\n\n   END_OF_CYCLE_GEMINI:\n      html->CurrOfs = html->Start_Ofs + token_start;\n   }/*while*/\n\n   /* save status */\n   html->in_pre = in_pre;\n\n   HT2TB(html)->flush ();\n\n   return token_start;\n}\n\n/*\n * Here's where we parse the gopher code and put it into the Textblock structure.\n * Return value: number of bytes parsed\n */\nstatic int Gopher_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof)\n{\n   char ch_str[2] = { 0 };\n   int token_start, buf_index;\n\n   char title_open[] = \"<title>\",  title_close[] = \"</title>\";\n   char body_open[] = \"<body>\";\n   char pre_open[] = \"<pre>\", pre_close[] = \"</pre>\";\n   char link_open[2048] = \"\", link_close[] = \"</a>\";\n   char input_tag[] = \"<input name=\\\"q\\\" placeholder=\\\"search text\\\" />\";\n   char submit_tag[] = \"<input type=\\\"submit\\\" />\";\n   char form_open[2048] = \"\", form_open_template[] = \"<form action=\\\"gopher://1::%s:%s%s\\\">\", form_close[] = \"</form>\";\n\n   bool in_line = false;\n\n   char line_type = 'i'; /* gophermap line type */\n\n   /* Set default title and open body tag at the beginning */\n   if (html->CurrOfs == 0) {\n      if(a_Url_hostname(html->page_url)) {\n         Html_process_tag(html, title_open, strlen(title_open));\n         Html_process_word(html, a_Url_hostname(html->page_url), strlen(a_Url_hostname(html->page_url)));\n         Html_process_tag(html, title_close, strlen(title_close));\n      }\n\n      Html_process_tag(html, body_open, strlen(body_open));\n   }\n\n   /* Now, 'buf' and 'bufsize' define a buffer aligned to start at a token\n    * boundary. Iterate through tokens until end of buffer is reached. */\n   buf_index = 0;\n   token_start = buf_index;\n   while ((buf_index < bufsize) && !html->stop_parser) {\n      /* invariant: buf_index == bufsize || token_start == buf_index */\n      \n      char *text, *resource, *host, *port, *end_of_line = NULL;\n\n      if(html->last_partial_line[0]) {\n         /* restore last partial line */\n\n         end_of_line = buf + strcspn(buf, \"\\n\");\n      \n         if(!end_of_line)\n            end_of_line = buf + bufsize;\n\n         if(*(end_of_line - 1) == '\\r')\n            *(end_of_line - 1) = '\\0';\n         else\n            *end_of_line = '\\0';\n\n         strncat(html->last_partial_line, buf, sizeof(html->last_partial_line) - 1);\n\n         /* get line type */\n         line_type = html->last_partial_line[0];\n\n         if(line_type == '.') {\n            /* end of gopher menu */\n            buf_index = end_of_line - buf + 1;\n            token_start = buf_index;\n            goto END_OF_CYCLE_GOPHER;\n         }\n\n         /* get text part */\n         text = html->last_partial_line + 1;\n\n      }\n\n      else {\n         /* line is new*/\n\n         /* get line type */\n         line_type = buf[buf_index];\n\n         if(line_type == '.') {\n            /* end of gopher menu */\n            buf_index += 3;\n            token_start = buf_index;\n            goto END_OF_CYCLE_GOPHER;\n         }\n\n         /* get text part */\n         text = buf + buf_index + 1;\n\n      }\n      \n      /* get resource */\n      resource = text + strcspn(text, \"\\t\");\n      resource++;\n\n      /* get host */\n      host = resource + strcspn(resource, \"\\t\");\n      host++;\n\n      /* get port */\n      port = host + strcspn(host, \"\\t\");\n      port++;\n\n      if (!html->last_partial_line[0]) {\n\n         /* get end of line */\n         end_of_line = port + strcspn(port, \"\\n\");\n      \n         if(*end_of_line != '\\n')\n            end_of_line = buf + bufsize;\n         else\n            *(end_of_line - 1) = '\\0';\n\n      }\n      \n      if(end_of_line - (buf + buf_index) >= (long) sizeof(html->last_partial_line)) {\n         printf(\"Long line: %ld\\n\", end_of_line - (buf + buf_index));\n         BUG_MSG(\"Line too long: skipping.\");\n         goto CLEANUP_GOPHER;\n      }\n\n      if(*end_of_line != '\\n' && !html->last_partial_line[0]) {\n\n         strncpy(html->last_partial_line, buf + buf_index, sizeof(html->last_partial_line) - 1);\n         goto CLEANUP_GOPHER;\n         \n      } else if (*end_of_line != '\\n') {\n\n         printf(\"Double broken line detected!\\n\");\n         BUG_MSG(\"Double broken line: skipping.\");\n         \n         goto CLEANUP_GOPHER;\n\n      }\n\n      *(resource - 1) = '\\0';\n      *(host - 1) = '\\0';\n      *(port - 1) = '\\0';\n\n      /* interpret line */\n\n      switch(line_type) {\n\n      case '7':\n         /* Gopher full-text search */\n         if(in_line) {\n            in_line = false;\n            Html_process_tag(html, pre_close, strlen(pre_close));\n         }\n         Html_process_tag(html, pre_open, strlen(pre_open));\n         ch_str[0] = line_type;\n         Html_process_word(html, \"[\", strlen(\"[\"));\n         Html_process_word(html, ch_str, strlen(ch_str));\n         Html_process_word(html, \"] \", strlen(\"] \"));\n         Html_process_word(html, text, strlen(text));\n         Html_process_word(html, \"\\n\", strlen(\"\\n\"));\n         snprintf(form_open, sizeof(form_open)-1, form_open_template,\n                  host, port, resource);\n         Html_process_tag(html, form_open, strlen(form_open));\n         Html_process_tag(html, input_tag, strlen(input_tag));\n         Html_process_tag(html, submit_tag, strlen(submit_tag));\n         Html_process_tag(html, form_close, strlen(form_close));\n         Html_process_tag(html, pre_close, strlen(pre_close));\n         break;\n         \n      case 'i':\n         /* Informational message */\n         if(!in_line) {\n            in_line = true;\n            Html_process_tag(html, pre_open, strlen(pre_open));\n         }\n         Html_process_word(html, text, strlen(text));\n         break;\n\n      case '0': /* Text file */\n      case '1': /* Gopher submenu */\n      case '2': /* CCSO Nameserver */\n      case '3': /* Error code returned by a Gopher server to indicate failure */\n      case '4': /* BinHex-encoded file (primarily for Macintosh computers) */\n      case '5': /* DOS file */\n      case '6': /* uuencoded file */\n      case '9': /* Binary file */\n      case '+': /* Mirror or alternate server (for load balancing) */\n      case 'g': /* GIF file */\n      case 'I': /* Image file */\n      case 'T': /* Telnet 3270 */\n      case ':': /* Bitmap image */\n      case ';': /* Movie file */\n      case '<': /* Sound file */\n      case 'd': /* Doc. Seen used alongside PDF's and .DOC's */\n      case 'h': /* HTML file */\n      case 'p': /* image file \"(especially the png format)\" */\n      case 'r': /* document rtf file (Rich Text Format) */\n      case 's': /* Sound file (especially the WAV format) */\n      case 'P': /* document pdf file (Portable Document Format) */\n      case 'X': /* document xml file (eXtensive Markup Language) */\n      default:\n         if(in_line) {\n            in_line = false;\n            Html_process_tag(html, pre_close, strlen(pre_close));\n         }\n         Html_process_tag(html, pre_open, strlen(pre_open));\n         ch_str[0] = line_type;\n         Html_process_word(html, \"[\", strlen(\"[\"));\n         Html_process_word(html, ch_str, strlen(ch_str));\n         Html_process_word(html, \"] \", strlen(\"] \"));\n         strncpy(link_open, \"<a href=\\\"\", 10);\n\n         if(!strncmp(resource, \"URL:\", 4)) {\n            strncat(link_open, resource + 4, sizeof(link_open) - 21);\n         } else {\n            strncat(link_open, \"gopher://\", 10);\n            strncat(link_open, ch_str, sizeof(link_open) - 21);\n            strncat(link_open, \"::\", sizeof(link_open) - 21);\n            strncat(link_open, host, sizeof(link_open) - 21);\n            strncat(link_open, \":\", 2);\n            strncat(link_open, port, sizeof(link_open) - 21);\n            strncat(link_open, resource, sizeof(link_open) - 21);\n         }\n         \n         strncat(link_open, \"\\\">\", 3);\n         Html_process_tag(html, link_open, strlen(link_open));\n         Html_process_word(html, text, strlen(text));\n         Html_process_tag(html, link_close, strlen(link_close));\n         Html_process_tag(html, pre_close, strlen(pre_close));\n         break;\n      }\n\n      Html_process_word(html, \"\\n\", 1);\n\n      /* cleanup */\n\n   CLEANUP_GOPHER:\n      \n      *(resource - 1) = '\\t';\n      *(host - 1) = '\\t';\n      *(port - 1) = '\\t';\n\n      if(*end_of_line == '\\n')\n         *(end_of_line - 1) = '\\r';\n      \n      buf_index = end_of_line - buf;\n      if(*end_of_line == '\\n') buf_index++;\n      token_start = buf_index;\n\n   END_OF_CYCLE_GOPHER:\n\n      if(end_of_line && *end_of_line == '\\n')\n        html->last_partial_line[0] = '\\0'; /* reset last partial line */\n      \n      html->CurrOfs = html->Start_Ofs + token_start;\n   }/*while*/\n\n   HT2TB(html)->flush ();\n\n   return token_start;\n}\n\n/*\n * Here's where we parse the markdown code and put it into the Textblock structure.\n * Return value: number of bytes parsed\n */\nstatic int validate_md_link(const char *buf, int bufsize) {\n   int i, level;\n\n   if(buf[0] != '[') /* first char must be open square [ */\n      return false;\n\n   for(i=1, level=1; i<bufsize; i++) {\n      if (buf[i] == '[')\n         level++;\n      else if (buf[i] == ']')\n         level--;\n      else if (buf[i] == '\\n')\n         return false;\n      if (level == 0)\n         break;\n   }\n\n   if (i == bufsize)\n      return false;\n   \n   if (++i  == bufsize || buf[i] != '(')\n      return false;\n   i++;\n\n   for(; i<bufsize; i++) {\n      if (buf[i] == ')')\n         break;\n      else if (buf[i] == '\\n')\n         return false;\n   }\n\n   return true;\n   \n}\n\nstatic int Markdown_write_raw(DilloHtml *html, char *buf, int bufsize, int Eof)\n{\n   char ch = 0;\n   int token_start, buf_index, tmp;\n\n   char title_open[] = \"<title>\",  title_close[] = \"</title>\";\n   char body_open[] = \"<body>\";\n   char par_open[] = \"<p>\", par_close[] = \"</p>\";\n   char heading_open[16] = \"<hX>\", heading_close[16] = \"</hX>\";\n   char pre_open[] = \"<pre>\", pre_close[] = \"</pre>\";\n   char pre_single_open[] = \"<tt>\", pre_single_close[] = \"</tt>\";\n   char quote_open[] = \"<blockquote>\", quote_close[] = \"</blockquote>\";\n   char img_tag[2048] = \"\", img_start_tag[] = \"<img src=\\\"\", img_middle_tag[] = \"\\\" alt=\\\"\", img_end_tag[] = \"\\\" />\";\n   char link_open[2048] = \"\", link_close[] = \"</a>\";\n   char list_open[] = \"<ul>\", list_close[] = \"</ul>\";\n   char list_elem_open[] = \"<li>\", list_elem_close[] = \"</li>\";\n   char bold_open[] = \"<b>\", bold_close[] = \"</b>\";\n   char em_open[] = \"<em>\", em_close[] = \"</em>\";\n   char strike_open[] = \"<strike>\", strike_close[] = \"</strike>\";\n   char hor_line[] = \"<hr/>\";\n\n   bool in_par, in_link, in_heading, in_pre, in_pre_single, in_quote, in_list_elem, in_middle_line, in_bold, in_em, in_strike;\n   in_par = in_link = in_heading = in_pre = in_pre_single = in_quote = in_list_elem = in_middle_line = in_bold = in_em = in_strike = false;\n\n   bool no_output = false;\n   \n   int link_level = 0;\n   int list_indents[8] = { -1 };\n   int list_level = -1, curr_indent = 0;\n\n   /* Restore flags */\n   in_pre = html->in_pre;\n   in_link = html->in_link;\n   list_level = html->list_level;\n\n   /* Set default title and open body tag at the beginning */\n   if (html->CurrOfs == 0 && !in_link) {\n      if(a_Url_hostname(html->page_url)) {\n         Html_process_tag(html, title_open, strlen(title_open));\n         Html_process_word(html, a_Url_hostname(html->page_url), strlen(a_Url_hostname(html->page_url)));\n         Html_process_tag(html, title_close, strlen(title_close));\n      }\n\n      Html_process_tag(html, body_open, strlen(body_open));\n   }\n\n   /* Now, 'buf' and 'bufsize' define a buffer aligned to start at a token\n    * boundary. Iterate through tokens until end of buffer is reached. */\n   buf_index = 0;\n   token_start = buf_index;\n   while ((buf_index < bufsize) && !html->stop_parser) {\n      /* invariant: buf_index == bufsize || token_start == buf_index */\n\n      if (list_level >= (int) (sizeof(list_indents) / sizeof(list_indents[0]))) {\n         /* check max list level */\n         BUG_MSG(\"Reached maximum sublist level.\");\n         while (list_level-- >= 0) {\n            Html_process_tag(html, list_close, strlen(list_close));\n         }\n      }\n\n      if(!in_middle_line && !in_pre) {\n         /* skip spaces at the beginning of the line and\n            estimate the possible indent level for a sublist */\n         curr_indent = 0;\n         if(NEXT1CH(' ') || NEXT1CH('\\t')) {\n            while (++buf_index < bufsize && (NEXT1CH(' ') || NEXT1CH('\\t'))) ;\n            curr_indent = buf_index - token_start;\n            token_start = buf_index;\n         }\n      }\n      \n      if (!in_middle_line && list_level >= 0 && !(NEXT2CH('-', ' ') || NEXT2CH('+', ' ') || NEXT2CH('*', ' '))) {\n         /* close multiline list and sublists */\n         do {\n            Html_process_tag(html, list_close, strlen(list_close));\n         } while(--list_level >= 0);\n      }\n\n      if(!in_middle_line) {\n         /* we are at the beginning of a line */\n\n         if (NEXT1CH('#')) {\n            /* open heading: determine the heading level (max level = 9) */\n            while (buf_index - token_start < 9 && ++buf_index < bufsize && buf[buf_index] == '#') ;\n            in_heading = true;\n            in_middle_line = true;\n            if(in_par) { /* close par if needed */\n               in_par = false;\n               Html_process_tag(html, par_close, strlen(par_close));\n            }\n            snprintf(heading_open, sizeof(heading_open), \"<h%d>\", buf_index - token_start);\n            snprintf(heading_close, sizeof(heading_close), \"</h%d>\", buf_index - token_start);\n            Html_process_tag(html, heading_open, strlen(heading_open));\n            while (buf_index < bufsize && buf[buf_index] != '\\n' && isspace(buf[buf_index])) buf_index++; /* skip spaces */\n            token_start = buf_index;\n         }\n\n         else if (NEXT3CH('`', '`', '`')) {\n            /* open/close pre */\n            while (++buf_index < bufsize && NEXT1CH('`')) ;\n            if(in_pre) {\n               in_pre = false;\n               Html_process_tag(html, pre_close, strlen(pre_close));\n            } else {\n               in_pre = true;\n               Html_process_tag(html, pre_open, strlen(pre_open));\n            }\n            while (buf_index < bufsize && (buf[buf_index] && buf[buf_index] != '\\n'))\n               buf_index++; /* skip to next line */\n            if (buf_index < bufsize && buf[buf_index] == '\\n') buf_index++;\n            token_start = buf_index;\n         }\n        \n         else if (NEXT1CH('>')) {\n            /* open quoted par */\n            buf_index++;\n            Html_process_tag(html, quote_open, strlen(quote_open));\n            in_quote = true;\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n      \n         else if (NEXT2CH('-', ' ') || NEXT2CH('+', ' ') || NEXT2CH('*', ' ')) {\n            /* open/close list and list element */\n            \n            while (++buf_index < bufsize && buf[buf_index] != '\\n' && isspace(buf[buf_index])) ;\n\n            if(list_level < 0 || list_indents[list_level] < curr_indent) {\n               /* beginning of a new multiline list/sublist */\n               list_indents[++list_level] = curr_indent;\n               Html_process_tag(html, list_open, strlen(list_open));\n            }\n\n            else if (list_indents[list_level] > curr_indent) {\n               /* closing of one or more sublists */\n\n               while (list_level >= 0 && list_indents[list_level] != curr_indent) {\n                  Html_process_tag(html, list_close, strlen(list_close));\n                  list_level--;\n               }\n               \n               if (list_level < 0) {\n                  BUG_MSG(\"Invalid list indentation.\");\n                  goto SKIP_WITHOUT_ELEMENT;\n               }\n               \n            }\n\n            /* add a new list element */\n            in_list_elem = true;\n            Html_process_tag(html, list_elem_open, strlen(list_elem_open)); /* new list element */\n            \n         SKIP_WITHOUT_ELEMENT:\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n\n         else if (NEXT1CH('\\n')) {\n            /* empty line: skip it */\n            buf_index++;\n            token_start = buf_index;\n         }\n\n         else {\n            /* no match in this section: continue to generic markup */\n            goto GENERIC_MD_MARKUP;\n         }\n\n         /* a match occurred: continue with main while loop */\n         goto END_OF_CYCLE_MD;\n         \n      }\n\n      else {\n         /* we are in the middle of a line */\n      \n         if (NEXT1CH('\\n') || NEXT1CH('\\0')) {\n            /* close line (and maybe section) */\n\n            if (in_pre_single) {\n               Html_process_tag(html, pre_single_close, strlen(pre_single_close));\n               in_pre_single = false;\n            }\n\n            if (in_list_elem) {\n               Html_process_tag(html, list_elem_close, strlen(list_elem_close));\n               in_list_elem = false;\n            }\n        \n            if (in_par) {\n               Html_process_tag(html, par_close, strlen(par_close));\n               in_par = false;\n            } else if (in_heading) {\n               Html_process_tag(html, heading_close, strlen(heading_close));\n               in_heading = false;\n            } else if (in_pre) {\n               Html_process_word(html, \"\\n\", 1);\n            } else if (in_quote) {\n               Html_process_tag(html, quote_close, strlen(quote_close));\n               in_quote = false;\n            }\n         \n            in_middle_line = false;\n            if (buf[buf_index] == '\\n') buf_index++;\n            token_start = buf_index;\n         }\n\n         else {\n            /* no match in this section: continue to generic markup */\n            goto GENERIC_MD_MARKUP;\n         }\n\n         /* a match occurred: continue with main while loop */\n         goto END_OF_CYCLE_MD;\n\n      }\n\n   GENERIC_MD_MARKUP:\n      /* markup that doesn't depend on its position in a line */\n      \n      if(!in_pre) {\n         /* we are not in a preformatted section */\n\n         if (NEXT3CH('-', '-', '-')) {\n            /* horizontal line */\n            while (++buf_index < bufsize && NEXT1CH('-')) ;\n            Html_process_tag(html, hor_line, strlen(hor_line));\n            token_start = buf_index;\n         }\n         \n         else if (NEXT1CH('`')) {\n            /* open/close pre single line */\n            buf_index++;\n            if (!in_middle_line && !in_par && !in_link && !in_pre) {\n               /* start new paragraph */\n               in_par = true;\n               Html_process_tag(html, par_open, strlen(par_open));\n            }\n            if(in_pre_single) {\n               in_pre_single = false;\n               Html_process_tag(html, pre_single_close, strlen(pre_single_close));\n            } else {\n               in_pre_single = true;\n               Html_process_tag(html, pre_single_open, strlen(pre_single_open));\n            }\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n\n         else if (NEXT3CH('<', 'p', '>') || NEXT3CH('<', 'p', ' ') || NEXT4CH('<', '/', 'p', '>')     || /* P tag */\n                  NEXT5CH('<', 'i', 'm', 'g', ' ') || NEXT6CH('<', '/', 'i', 'm', 'g', '>')           || /* IMG tag */\n                  NEXT3CH('<', 'a', ' ') || NEXT4CH('<', '/', 'a', '>')                               || /* A tag */\n                  NEXT6CH('<', 's', 'p', 'a', 'n', ' ') || NEXT7CH('<', '/', 's', 'p', 'a', 'n', '>') || /* SPAN tag */\n                  NEXT5CH('<', 's', 'u', 'b', '>') || NEXT6CH('<', '/', 's', 'u', 'b', '>')           || /* SUB tag */\n                  NEXT5CH('<', 's', 'u', 'p', '>') || NEXT6CH('<', '/', 's', 'u', 'p', '>')           || /* SUP tag */\n                  NEXT4CH('<', '!', '-', '-')                                                         || /* HTML comment */\n                  0) {\n            /* supported html tags */\n\n            if (!in_middle_line && !in_par && !in_link && !buf[buf_index+1] &&\n                !(NEXT3CH('<', 'p', '>') || NEXT3CH('<', 'p', ' '))) {\n               /* start new paragraph */\n               in_par = true;\n               Html_process_tag(html, par_open, strlen(par_open));\n            }\n            \n            if (in_par && NEXT4CH('<', '/', 'p', '>')) {\n               in_par = false;\n            }\n            \n            if (NEXT6CH('<', '/', 'i', 'm', 'g', '>') || NEXT4CH('<', '!', '-', '-')) /* some tags do not require output */\n               no_output = true;\n\n            while (++buf_index < bufsize && buf[buf_index] != '>') ;\n            buf_index++;\n\n            if(!no_output)\n               Html_process_tag(html, buf + token_start, buf_index - token_start);\n\n            no_output = false;\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n\n         else if (!in_pre_single && NEXT2CH('~', '~')) {\n            /* strikethrough text */\n            buf_index += 2;\n            if (!in_middle_line && !in_par && !in_link && !in_pre) {\n               /* start new paragraph */\n               in_par = true;\n               Html_process_tag(html, par_open, strlen(par_open));\n            }\n            if(in_strike) {\n               in_strike = false;\n               Html_process_tag(html, strike_close, strlen(strike_close));\n            } else {\n               in_strike = true;\n               Html_process_tag(html, strike_open, strlen(strike_open));\n            }\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n\n         else if (!in_pre_single && (NEXT2CH('*', '*') || NEXT2CH('_', '_'))) {\n            /* bold text */\n            buf_index += 2;\n            if (!in_middle_line && !in_par && !in_link && !in_pre) {\n               /* start new paragraph */\n               in_par = true;\n               Html_process_tag(html, par_open, strlen(par_open));\n            }\n            if(in_bold) {\n               in_bold = false;\n               Html_process_tag(html, bold_close, strlen(bold_close));\n            } else {\n               in_bold = true;\n               Html_process_tag(html, bold_open, strlen(bold_open));\n            }\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n\n         else if (!in_pre_single && (NEXT1CH('*') || NEXT1CH('_'))) {\n            /* italic text */\n            buf_index++;\n            if (!in_middle_line && !in_par && !in_link && !in_pre) {\n               /* start new paragraph */\n               in_par = true;\n               Html_process_tag(html, par_open, strlen(par_open));\n            }\n            if (buf_index > 1 && buf[buf_index-2] == '\\\\') {\n               ch = buf[buf_index];\n               buf[buf_index] = '\\0';\n               Html_process_word(html, buf + token_start, buf_index - token_start);\n               buf[buf_index] = ch;\n            } else if(in_em) {\n               in_em = false;\n               Html_process_tag(html, em_close, strlen(em_close));\n            } else {\n               in_em = true;\n               Html_process_tag(html, em_open, strlen(em_open));\n            }\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n\n         else if (NEXT2CH('!', '[') && validate_md_link(buf + buf_index + 1, bufsize - buf_index - 1)) {\n            /* image: determine src and text */\n            buf_index += 2;\n            if (!in_middle_line && !in_par && !in_link && !in_pre) {\n               /* start new paragraph */\n               in_par = true;\n               Html_process_tag(html, par_open, strlen(par_open));\n            }\n            link_level=1;\n            while (buf_index < bufsize) { /* find end of alt text */\n               if(buf[buf_index] == '[')\n                  link_level++;\n               else if(buf[buf_index] == ']')\n                  link_level--;\n               else if(buf[buf_index] == '\\n' || buf[buf_index] == '\\0')\n                  break;\n               if(link_level == 0)\n                  break;\n               buf_index++;\n            }\n            if(!(buf_index+1 < bufsize) || buf[++buf_index] != '(') {\n               /* not a link */\n               Html_process_word(html, buf + token_start,\n                                 buf_index - token_start);\n            } else {\n               tmp = buf_index++;\n               while (buf_index < bufsize && buf[buf_index] != ')') buf_index++; /* find end of src */\n               buf[buf_index] = '\\0';\n               strncpy(img_tag, img_start_tag, sizeof(img_tag) - 1);\n               if (strlen(buf + tmp + 1) < ((sizeof(img_tag) - 200) / 2)) /* note: we keep a larger margin than necessary */\n                  strncat(img_tag, buf + tmp + 1, ((sizeof(img_tag) - 200) / 2));\n               else\n                  BUG_MSG(\"Image src attribute too long\");\n               strncat(img_tag, img_middle_tag, sizeof(img_tag) - strlen(img_tag) - 1);\n               buf[buf_index] = ')';\n               buf_index++;\n               buf[tmp-1] = '\\0';\n               if (strlen(buf + token_start + 2) < ((sizeof(img_tag) - 200) / 2)) /* note: we keep a larger margin than necessary */\n                  strncat(img_tag, buf + token_start + 2,((sizeof(img_tag) - 200) / 2));\n               else\n                  BUG_MSG(\"Image alt text attribute too long\");\n               strncat(img_tag, img_end_tag, sizeof(img_tag) - strlen(img_tag) - 1);\n               buf[tmp-1] = ']';\n               Html_process_tag(html, img_tag, strlen(img_tag)); /* send img tag */\n            }\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n         \n         else if (NEXT1CH('[') && validate_md_link(buf + buf_index, bufsize - buf_index)) {\n            /* link: determine href and text */\n            buf_index++;\n            if (!in_middle_line && !in_par && !in_link && !in_pre) {\n               /* start new paragraph */\n               in_par = true;\n               Html_process_tag(html, par_open, strlen(par_open));\n            }\n            link_level=1;\n            while (buf_index < bufsize) { /* find end of text */\n               if(buf[buf_index] == '[')\n                  link_level++;\n               else if(buf[buf_index] == ']')\n                  link_level--;\n               else if(buf[buf_index] == '\\n' || buf[buf_index] == '\\0')\n                  break;\n               if(link_level == 0)\n                  break;\n               buf_index++;\n            }\n            if(!(buf_index+1 < bufsize) || buf[++buf_index] != '(') {\n               /* not a link */\n               Html_process_word(html, buf + token_start,\n                                 buf_index - token_start);\n            } else {\n               tmp = buf_index++;\n               while (buf_index < bufsize && buf[buf_index] != ')') buf_index++; /* find end of href */\n               buf[buf_index] = '\\0';\n               strncpy(link_open, \"<a href=\\\"\", 10);\n               strncat(link_open, buf + tmp + 1, sizeof(link_open) - 12);\n               strncat(link_open, \"\\\">\", 3);\n               buf[buf_index] = ')';\n               Html_process_tag(html, link_open, strlen(link_open)); /* send open link tag */\n               buf_index++;\n               buf[tmp-1] = '\\0';\n               /* process link content recursively */\n               html->in_link = true;\n               Markdown_write_raw(html, buf + token_start + 1, tmp - token_start - 2, 1);\n               html->in_link = false;\n               buf[tmp-1] = ']';\n               Html_process_tag(html, link_close, strlen(link_close)); /* send link close tag */\n            }\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n\n         else if (NEXT1CH('<') || NEXT1CH('~') || NEXT1CH('[')) {\n            /* just characters: process as word */\n            buf_index++;\n            if (!in_middle_line && !in_par && !in_link && !in_pre) {\n               /* start new paragraph */\n               in_par = true;\n               Html_process_tag(html, par_open, strlen(par_open));\n            }\n            ch = buf[buf_index];\n            buf[buf_index] = '\\0';\n            Html_process_word(html, buf + token_start, buf_index - token_start);\n            buf[buf_index] = ch;\n            in_middle_line = true;\n            token_start = buf_index;\n         }\n\n         else {\n            /* no match in this section: continue to preformatted indipendent text */\n            goto GENERIC_MD_MARKUP_PRE_INDIPENDENT;\n         }\n\n         /* a match occurred: continue with main while loop */\n         goto END_OF_CYCLE_MD;\n\n      }\n\n   GENERIC_MD_MARKUP_PRE_INDIPENDENT:\n\n      if (isspace(buf[buf_index])) {\n         /* whitespace: group all available whitespace */\n         if (!in_middle_line && !in_par && !in_link && !in_pre) {\n            /* start new paragraph */\n            in_par = true;\n            Html_process_tag(html, par_open, strlen(par_open));\n         }\n         while (++buf_index < bufsize && isspace(buf[buf_index]) && buf[buf_index] != '\\n') ;\n         Html_process_space(html, buf + token_start, buf_index - token_start);\n         in_middle_line = true;\n         token_start = buf_index;\n      }\n\n      else {\n         /* a word: add to section */\n\n         if (!in_middle_line && !in_par && !in_link && !in_pre) {\n            /* start new paragraph */\n            in_par = true;\n            in_middle_line = true;\n            Html_process_tag(html, par_open, strlen(par_open));\n         } else if (!in_middle_line && (in_pre || in_link)) {\n            in_middle_line = true;\n         } else if (!in_middle_line) {\n            in_middle_line = true;\n            BUG_MSG(\"Word outside a markdown section\");\n         }\n\n         if(!in_pre) \n            while (buf_index < bufsize) {\n               buf_index += strcspn(buf + buf_index, \" _~`*<[\\n\");\n               if (buf[buf_index] == '_' && isalnum(buf[buf_index + 1]) && isalnum(buf[buf_index - 1])) {\n                  buf_index++;\n                  continue;\n               }\n               break;\n            }\n         else\n            buf_index += strcspn(buf + buf_index, \" \\n\");\n\n         if (buf_index < bufsize || Eof) {\n            /* successfully found end of word */\n            ch = buf[buf_index];\n            buf[buf_index] = 0;\n            Html_process_word(html, buf + token_start,\n                              buf_index - token_start);\n            buf[buf_index] = ch;\n            token_start = buf_index;\n         }\n      }\n\n   END_OF_CYCLE_MD:\n\n      html->CurrOfs = html->Start_Ofs + token_start;\n   }/*while*/\n\n   if (in_link) {\n      /* close all pending tags if we are inside the content of a link */\n      \n      if (in_pre_single) {\n         Html_process_tag(html, pre_single_close, strlen(pre_single_close));\n      }\n\n      if (in_list_elem) {\n         Html_process_tag(html, list_elem_close, strlen(list_elem_close));\n      }\n        \n      if (in_par) {\n         Html_process_tag(html, par_close, strlen(par_close));\n      }\n\n      if (in_heading) {\n         Html_process_tag(html, heading_close, strlen(heading_close));\n      }\n\n      if (in_pre) {\n         Html_process_tag(html, pre_close, strlen(pre_close));\n      }\n\n      if (in_quote) {\n         Html_process_tag(html, quote_close, strlen(quote_close));\n      }\n\n      if (in_bold) {\n         Html_process_tag(html, bold_close, strlen(bold_close));\n      }\n\n      if (in_em) {\n         Html_process_tag(html, em_close, strlen(em_close));\n      }\n\n      if (in_strike) {\n         Html_process_tag(html, strike_close, strlen(strike_close));\n      }\n\n   }\n\n   /* save status */\n   html->in_pre = in_pre;\n   html->in_link = in_link;\n   html->list_level = list_level;\n\n   HT2TB(html)->flush ();\n\n   return token_start;\n}\n"
  },
  {
    "path": "src/html.hh",
    "content": "#ifndef __HTML_HH__\n#define __HTML_HH__\n\n#include \"url.h\"               // for DilloUrl\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n * Exported functions\n */\nvoid a_Html_load_images(void *v_html, DilloUrl *pattern);\nvoid a_Html_form_submit(void *v_html, void *v_form);\nvoid a_Html_form_reset(void *v_html, void *v_form);\nvoid a_Html_form_display_hiddens(void *v_html, void *v_form, bool_t display);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n\n#endif /* __HTML_HH__ */\n"
  },
  {
    "path": "src/html_charrefs.h",
    "content": "#ifndef HTML_CHARREFS_H\n#define HTML_CHARREFS_H\n\ntypedef struct {\n   const char *ref;\n   const char *html5_str;\n   const char *html4_str;\n} Charref_t;\n\n#define NumRef 2125\nstatic const Charref_t Charrefs[NumRef] = {\n{\"AElig\", \"Æ\", \"Æ\"},\n{\"AMP\", \"&\", NULL},\n{\"Aacute\", \"Á\", \"Á\"},\n{\"Abreve\", \"Ă\", NULL},\n{\"Acirc\", \"Â\", \"Â\"},\n{\"Acy\", \"А\", NULL},\n{\"Afr\", \"𝔄\", NULL},\n{\"Agrave\", \"À\", \"À\"},\n{\"Alpha\", \"Α\", \"Α\"},\n{\"Amacr\", \"Ā\", NULL},\n{\"And\", \"⩓\", NULL},\n{\"Aogon\", \"Ą\", NULL},\n{\"Aopf\", \"𝔸\", NULL},\n{\"ApplyFunction\", \"⁡\", NULL},\n{\"Aring\", \"Å\", \"Å\"},\n{\"Ascr\", \"𝒜\", NULL},\n{\"Assign\", \"≔\", NULL},\n{\"Atilde\", \"Ã\", \"Ã\"},\n{\"Auml\", \"Ä\", \"Ä\"},\n{\"Backslash\", \"∖\", NULL},\n{\"Barv\", \"⫧\", NULL},\n{\"Barwed\", \"⌆\", NULL},\n{\"Bcy\", \"Б\", NULL},\n{\"Because\", \"∵\", NULL},\n{\"Bernoullis\", \"ℬ\", NULL},\n{\"Beta\", \"Β\", \"Β\"},\n{\"Bfr\", \"𝔅\", NULL},\n{\"Bopf\", \"𝔹\", NULL},\n{\"Breve\", \"˘\", NULL},\n{\"Bscr\", \"ℬ\", NULL},\n{\"Bumpeq\", \"≎\", NULL},\n{\"CHcy\", \"Ч\", NULL},\n{\"COPY\", \"©\", NULL},\n{\"Cacute\", \"Ć\", NULL},\n{\"Cap\", \"⋒\", NULL},\n{\"CapitalDifferentialD\", \"ⅅ\", NULL},\n{\"Cayleys\", \"ℭ\", NULL},\n{\"Ccaron\", \"Č\", NULL},\n{\"Ccedil\", \"Ç\", \"Ç\"},\n{\"Ccirc\", \"Ĉ\", NULL},\n{\"Cconint\", \"∰\", NULL},\n{\"Cdot\", \"Ċ\", NULL},\n{\"Cedilla\", \"¸\", NULL},\n{\"CenterDot\", \"·\", NULL},\n{\"Cfr\", \"ℭ\", NULL},\n{\"Chi\", \"Χ\", \"Χ\"},\n{\"CircleDot\", \"⊙\", NULL},\n{\"CircleMinus\", \"⊖\", NULL},\n{\"CirclePlus\", \"⊕\", NULL},\n{\"CircleTimes\", \"⊗\", NULL},\n{\"ClockwiseContourIntegral\", \"∲\", NULL},\n{\"CloseCurlyDoubleQuote\", \"”\", NULL},\n{\"CloseCurlyQuote\", \"’\", NULL},\n{\"Colon\", \"∷\", NULL},\n{\"Colone\", \"⩴\", NULL},\n{\"Congruent\", \"≡\", NULL},\n{\"Conint\", \"∯\", NULL},\n{\"ContourIntegral\", \"∮\", NULL},\n{\"Copf\", \"ℂ\", NULL},\n{\"Coproduct\", \"∐\", NULL},\n{\"CounterClockwiseContourIntegral\", \"∳\", NULL},\n{\"Cross\", \"⨯\", NULL},\n{\"Cscr\", \"𝒞\", NULL},\n{\"Cup\", \"⋓\", NULL},\n{\"CupCap\", \"≍\", NULL},\n{\"DD\", \"ⅅ\", NULL},\n{\"DDotrahd\", \"⤑\", NULL},\n{\"DJcy\", \"Ђ\", NULL},\n{\"DScy\", \"Ѕ\", NULL},\n{\"DZcy\", \"Џ\", NULL},\n{\"Dagger\", \"‡\", \"‡\"},\n{\"Darr\", \"↡\", NULL},\n{\"Dashv\", \"⫤\", NULL},\n{\"Dcaron\", \"Ď\", NULL},\n{\"Dcy\", \"Д\", NULL},\n{\"Del\", \"∇\", NULL},\n{\"Delta\", \"Δ\", \"Δ\"},\n{\"Dfr\", \"𝔇\", NULL},\n{\"DiacriticalAcute\", \"´\", NULL},\n{\"DiacriticalDot\", \"˙\", NULL},\n{\"DiacriticalDoubleAcute\", \"˝\", NULL},\n{\"DiacriticalGrave\", \"`\", NULL},\n{\"DiacriticalTilde\", \"˜\", NULL},\n{\"Diamond\", \"⋄\", NULL},\n{\"DifferentialD\", \"ⅆ\", NULL},\n{\"Dopf\", \"𝔻\", NULL},\n{\"Dot\", \"¨\", NULL},\n{\"DotDot\", \"⃜\", NULL},\n{\"DotEqual\", \"≐\", NULL},\n{\"DoubleContourIntegral\", \"∯\", NULL},\n{\"DoubleDot\", \"¨\", NULL},\n{\"DoubleDownArrow\", \"⇓\", NULL},\n{\"DoubleLeftArrow\", \"⇐\", NULL},\n{\"DoubleLeftRightArrow\", \"⇔\", NULL},\n{\"DoubleLeftTee\", \"⫤\", NULL},\n{\"DoubleLongLeftArrow\", \"⟸\", NULL},\n{\"DoubleLongLeftRightArrow\", \"⟺\", NULL},\n{\"DoubleLongRightArrow\", \"⟹\", NULL},\n{\"DoubleRightArrow\", \"⇒\", NULL},\n{\"DoubleRightTee\", \"⊨\", NULL},\n{\"DoubleUpArrow\", \"⇑\", NULL},\n{\"DoubleUpDownArrow\", \"⇕\", NULL},\n{\"DoubleVerticalBar\", \"∥\", NULL},\n{\"DownArrow\", \"↓\", NULL},\n{\"DownArrowBar\", \"⤓\", NULL},\n{\"DownArrowUpArrow\", \"⇵\", NULL},\n{\"DownBreve\", \"̑\", NULL},\n{\"DownLeftRightVector\", \"⥐\", NULL},\n{\"DownLeftTeeVector\", \"⥞\", NULL},\n{\"DownLeftVector\", \"↽\", NULL},\n{\"DownLeftVectorBar\", \"⥖\", NULL},\n{\"DownRightTeeVector\", \"⥟\", NULL},\n{\"DownRightVector\", \"⇁\", NULL},\n{\"DownRightVectorBar\", \"⥗\", NULL},\n{\"DownTee\", \"⊤\", NULL},\n{\"DownTeeArrow\", \"↧\", NULL},\n{\"Downarrow\", \"⇓\", NULL},\n{\"Dscr\", \"𝒟\", NULL},\n{\"Dstrok\", \"Đ\", NULL},\n{\"ENG\", \"Ŋ\", NULL},\n{\"ETH\", \"Ð\", \"Ð\"},\n{\"Eacute\", \"É\", \"É\"},\n{\"Ecaron\", \"Ě\", NULL},\n{\"Ecirc\", \"Ê\", \"Ê\"},\n{\"Ecy\", \"Э\", NULL},\n{\"Edot\", \"Ė\", NULL},\n{\"Efr\", \"𝔈\", NULL},\n{\"Egrave\", \"È\", \"È\"},\n{\"Element\", \"∈\", NULL},\n{\"Emacr\", \"Ē\", NULL},\n{\"EmptySmallSquare\", \"◻\", NULL},\n{\"EmptyVerySmallSquare\", \"▫\", NULL},\n{\"Eogon\", \"Ę\", NULL},\n{\"Eopf\", \"𝔼\", NULL},\n{\"Epsilon\", \"Ε\", \"Ε\"},\n{\"Equal\", \"⩵\", NULL},\n{\"EqualTilde\", \"≂\", NULL},\n{\"Equilibrium\", \"⇌\", NULL},\n{\"Escr\", \"ℰ\", NULL},\n{\"Esim\", \"⩳\", NULL},\n{\"Eta\", \"Η\", \"Η\"},\n{\"Euml\", \"Ë\", \"Ë\"},\n{\"Exists\", \"∃\", NULL},\n{\"ExponentialE\", \"ⅇ\", NULL},\n{\"Fcy\", \"Ф\", NULL},\n{\"Ffr\", \"𝔉\", NULL},\n{\"FilledSmallSquare\", \"◼\", NULL},\n{\"FilledVerySmallSquare\", \"▪\", NULL},\n{\"Fopf\", \"𝔽\", NULL},\n{\"ForAll\", \"∀\", NULL},\n{\"Fouriertrf\", \"ℱ\", NULL},\n{\"Fscr\", \"ℱ\", NULL},\n{\"GJcy\", \"Ѓ\", NULL},\n{\"GT\", \">\", NULL},\n{\"Gamma\", \"Γ\", \"Γ\"},\n{\"Gammad\", \"Ϝ\", NULL},\n{\"Gbreve\", \"Ğ\", NULL},\n{\"Gcedil\", \"Ģ\", NULL},\n{\"Gcirc\", \"Ĝ\", NULL},\n{\"Gcy\", \"Г\", NULL},\n{\"Gdot\", \"Ġ\", NULL},\n{\"Gfr\", \"𝔊\", NULL},\n{\"Gg\", \"⋙\", NULL},\n{\"Gopf\", \"𝔾\", NULL},\n{\"GreaterEqual\", \"≥\", NULL},\n{\"GreaterEqualLess\", \"⋛\", NULL},\n{\"GreaterFullEqual\", \"≧\", NULL},\n{\"GreaterGreater\", \"⪢\", NULL},\n{\"GreaterLess\", \"≷\", NULL},\n{\"GreaterSlantEqual\", \"⩾\", NULL},\n{\"GreaterTilde\", \"≳\", NULL},\n{\"Gscr\", \"𝒢\", NULL},\n{\"Gt\", \"≫\", NULL},\n{\"HARDcy\", \"Ъ\", NULL},\n{\"Hacek\", \"ˇ\", NULL},\n{\"Hat\", \"^\", NULL},\n{\"Hcirc\", \"Ĥ\", NULL},\n{\"Hfr\", \"ℌ\", NULL},\n{\"HilbertSpace\", \"ℋ\", NULL},\n{\"Hopf\", \"ℍ\", NULL},\n{\"HorizontalLine\", \"─\", NULL},\n{\"Hscr\", \"ℋ\", NULL},\n{\"Hstrok\", \"Ħ\", NULL},\n{\"HumpDownHump\", \"≎\", NULL},\n{\"HumpEqual\", \"≏\", NULL},\n{\"IEcy\", \"Е\", NULL},\n{\"IJlig\", \"Ĳ\", NULL},\n{\"IOcy\", \"Ё\", NULL},\n{\"Iacute\", \"Í\", \"Í\"},\n{\"Icirc\", \"Î\", \"Î\"},\n{\"Icy\", \"И\", NULL},\n{\"Idot\", \"İ\", NULL},\n{\"Ifr\", \"ℑ\", NULL},\n{\"Igrave\", \"Ì\", \"Ì\"},\n{\"Im\", \"ℑ\", NULL},\n{\"Imacr\", \"Ī\", NULL},\n{\"ImaginaryI\", \"ⅈ\", NULL},\n{\"Implies\", \"⇒\", NULL},\n{\"Int\", \"∬\", NULL},\n{\"Integral\", \"∫\", NULL},\n{\"Intersection\", \"⋂\", NULL},\n{\"InvisibleComma\", \"⁣\", NULL},\n{\"InvisibleTimes\", \"⁢\", NULL},\n{\"Iogon\", \"Į\", NULL},\n{\"Iopf\", \"𝕀\", NULL},\n{\"Iota\", \"Ι\", \"Ι\"},\n{\"Iscr\", \"ℐ\", NULL},\n{\"Itilde\", \"Ĩ\", NULL},\n{\"Iukcy\", \"І\", NULL},\n{\"Iuml\", \"Ï\", \"Ï\"},\n{\"Jcirc\", \"Ĵ\", NULL},\n{\"Jcy\", \"Й\", NULL},\n{\"Jfr\", \"𝔍\", NULL},\n{\"Jopf\", \"𝕁\", NULL},\n{\"Jscr\", \"𝒥\", NULL},\n{\"Jsercy\", \"Ј\", NULL},\n{\"Jukcy\", \"Є\", NULL},\n{\"KHcy\", \"Х\", NULL},\n{\"KJcy\", \"Ќ\", NULL},\n{\"Kappa\", \"Κ\", \"Κ\"},\n{\"Kcedil\", \"Ķ\", NULL},\n{\"Kcy\", \"К\", NULL},\n{\"Kfr\", \"𝔎\", NULL},\n{\"Kopf\", \"𝕂\", NULL},\n{\"Kscr\", \"𝒦\", NULL},\n{\"LJcy\", \"Љ\", NULL},\n{\"LT\", \"<\", NULL},\n{\"Lacute\", \"Ĺ\", NULL},\n{\"Lambda\", \"Λ\", \"Λ\"},\n{\"Lang\", \"⟪\", NULL},\n{\"Laplacetrf\", \"ℒ\", NULL},\n{\"Larr\", \"↞\", NULL},\n{\"Lcaron\", \"Ľ\", NULL},\n{\"Lcedil\", \"Ļ\", NULL},\n{\"Lcy\", \"Л\", NULL},\n{\"LeftAngleBracket\", \"⟨\", NULL},\n{\"LeftArrow\", \"←\", NULL},\n{\"LeftArrowBar\", \"⇤\", NULL},\n{\"LeftArrowRightArrow\", \"⇆\", NULL},\n{\"LeftCeiling\", \"⌈\", NULL},\n{\"LeftDoubleBracket\", \"⟦\", NULL},\n{\"LeftDownTeeVector\", \"⥡\", NULL},\n{\"LeftDownVector\", \"⇃\", NULL},\n{\"LeftDownVectorBar\", \"⥙\", NULL},\n{\"LeftFloor\", \"⌊\", NULL},\n{\"LeftRightArrow\", \"↔\", NULL},\n{\"LeftRightVector\", \"⥎\", NULL},\n{\"LeftTee\", \"⊣\", NULL},\n{\"LeftTeeArrow\", \"↤\", NULL},\n{\"LeftTeeVector\", \"⥚\", NULL},\n{\"LeftTriangle\", \"⊲\", NULL},\n{\"LeftTriangleBar\", \"⧏\", NULL},\n{\"LeftTriangleEqual\", \"⊴\", NULL},\n{\"LeftUpDownVector\", \"⥑\", NULL},\n{\"LeftUpTeeVector\", \"⥠\", NULL},\n{\"LeftUpVector\", \"↿\", NULL},\n{\"LeftUpVectorBar\", \"⥘\", NULL},\n{\"LeftVector\", \"↼\", NULL},\n{\"LeftVectorBar\", \"⥒\", NULL},\n{\"Leftarrow\", \"⇐\", NULL},\n{\"Leftrightarrow\", \"⇔\", NULL},\n{\"LessEqualGreater\", \"⋚\", NULL},\n{\"LessFullEqual\", \"≦\", NULL},\n{\"LessGreater\", \"≶\", NULL},\n{\"LessLess\", \"⪡\", NULL},\n{\"LessSlantEqual\", \"⩽\", NULL},\n{\"LessTilde\", \"≲\", NULL},\n{\"Lfr\", \"𝔏\", NULL},\n{\"Ll\", \"⋘\", NULL},\n{\"Lleftarrow\", \"⇚\", NULL},\n{\"Lmidot\", \"Ŀ\", NULL},\n{\"LongLeftArrow\", \"⟵\", NULL},\n{\"LongLeftRightArrow\", \"⟷\", NULL},\n{\"LongRightArrow\", \"⟶\", NULL},\n{\"Longleftarrow\", \"⟸\", NULL},\n{\"Longleftrightarrow\", \"⟺\", NULL},\n{\"Longrightarrow\", \"⟹\", NULL},\n{\"Lopf\", \"𝕃\", NULL},\n{\"LowerLeftArrow\", \"↙\", NULL},\n{\"LowerRightArrow\", \"↘\", NULL},\n{\"Lscr\", \"ℒ\", NULL},\n{\"Lsh\", \"↰\", NULL},\n{\"Lstrok\", \"Ł\", NULL},\n{\"Lt\", \"≪\", NULL},\n{\"Map\", \"⤅\", NULL},\n{\"Mcy\", \"М\", NULL},\n{\"MediumSpace\", \" \", NULL},\n{\"Mellintrf\", \"ℳ\", NULL},\n{\"Mfr\", \"𝔐\", NULL},\n{\"MinusPlus\", \"∓\", NULL},\n{\"Mopf\", \"𝕄\", NULL},\n{\"Mscr\", \"ℳ\", NULL},\n{\"Mu\", \"Μ\", \"Μ\"},\n{\"NJcy\", \"Њ\", NULL},\n{\"Nacute\", \"Ń\", NULL},\n{\"Ncaron\", \"Ň\", NULL},\n{\"Ncedil\", \"Ņ\", NULL},\n{\"Ncy\", \"Н\", NULL},\n{\"NegativeMediumSpace\", \"​\", NULL},\n{\"NegativeThickSpace\", \"​\", NULL},\n{\"NegativeThinSpace\", \"​\", NULL},\n{\"NegativeVeryThinSpace\", \"​\", NULL},\n{\"NestedGreaterGreater\", \"≫\", NULL},\n{\"NestedLessLess\", \"≪\", NULL},\n{\"NewLine\", \"\\n\", NULL},\n{\"Nfr\", \"𝔑\", NULL},\n{\"NoBreak\", \"⁠\", NULL},\n{\"NonBreakingSpace\", \" \", NULL},\n{\"Nopf\", \"ℕ\", NULL},\n{\"Not\", \"⫬\", NULL},\n{\"NotCongruent\", \"≢\", NULL},\n{\"NotCupCap\", \"≭\", NULL},\n{\"NotDoubleVerticalBar\", \"∦\", NULL},\n{\"NotElement\", \"∉\", NULL},\n{\"NotEqual\", \"≠\", NULL},\n{\"NotEqualTilde\", \"≂̸\", NULL},\n{\"NotExists\", \"∄\", NULL},\n{\"NotGreater\", \"≯\", NULL},\n{\"NotGreaterEqual\", \"≱\", NULL},\n{\"NotGreaterFullEqual\", \"≧̸\", NULL},\n{\"NotGreaterGreater\", \"≫̸\", NULL},\n{\"NotGreaterLess\", \"≹\", NULL},\n{\"NotGreaterSlantEqual\", \"⩾̸\", NULL},\n{\"NotGreaterTilde\", \"≵\", NULL},\n{\"NotHumpDownHump\", \"≎̸\", NULL},\n{\"NotHumpEqual\", \"≏̸\", NULL},\n{\"NotLeftTriangle\", \"⋪\", NULL},\n{\"NotLeftTriangleBar\", \"⧏̸\", NULL},\n{\"NotLeftTriangleEqual\", \"⋬\", NULL},\n{\"NotLess\", \"≮\", NULL},\n{\"NotLessEqual\", \"≰\", NULL},\n{\"NotLessGreater\", \"≸\", NULL},\n{\"NotLessLess\", \"≪̸\", NULL},\n{\"NotLessSlantEqual\", \"⩽̸\", NULL},\n{\"NotLessTilde\", \"≴\", NULL},\n{\"NotNestedGreaterGreater\", \"⪢̸\", NULL},\n{\"NotNestedLessLess\", \"⪡̸\", NULL},\n{\"NotPrecedes\", \"⊀\", NULL},\n{\"NotPrecedesEqual\", \"⪯̸\", NULL},\n{\"NotPrecedesSlantEqual\", \"⋠\", NULL},\n{\"NotReverseElement\", \"∌\", NULL},\n{\"NotRightTriangle\", \"⋫\", NULL},\n{\"NotRightTriangleBar\", \"⧐̸\", NULL},\n{\"NotRightTriangleEqual\", \"⋭\", NULL},\n{\"NotSquareSubset\", \"⊏̸\", NULL},\n{\"NotSquareSubsetEqual\", \"⋢\", NULL},\n{\"NotSquareSuperset\", \"⊐̸\", NULL},\n{\"NotSquareSupersetEqual\", \"⋣\", NULL},\n{\"NotSubset\", \"⊂⃒\", NULL},\n{\"NotSubsetEqual\", \"⊈\", NULL},\n{\"NotSucceeds\", \"⊁\", NULL},\n{\"NotSucceedsEqual\", \"⪰̸\", NULL},\n{\"NotSucceedsSlantEqual\", \"⋡\", NULL},\n{\"NotSucceedsTilde\", \"≿̸\", NULL},\n{\"NotSuperset\", \"⊃⃒\", NULL},\n{\"NotSupersetEqual\", \"⊉\", NULL},\n{\"NotTilde\", \"≁\", NULL},\n{\"NotTildeEqual\", \"≄\", NULL},\n{\"NotTildeFullEqual\", \"≇\", NULL},\n{\"NotTildeTilde\", \"≉\", NULL},\n{\"NotVerticalBar\", \"∤\", NULL},\n{\"Nscr\", \"𝒩\", NULL},\n{\"Ntilde\", \"Ñ\", \"Ñ\"},\n{\"Nu\", \"Ν\", \"Ν\"},\n{\"OElig\", \"Œ\", \"Œ\"},\n{\"Oacute\", \"Ó\", \"Ó\"},\n{\"Ocirc\", \"Ô\", \"Ô\"},\n{\"Ocy\", \"О\", NULL},\n{\"Odblac\", \"Ő\", NULL},\n{\"Ofr\", \"𝔒\", NULL},\n{\"Ograve\", \"Ò\", \"Ò\"},\n{\"Omacr\", \"Ō\", NULL},\n{\"Omega\", \"Ω\", \"Ω\"},\n{\"Omicron\", \"Ο\", \"Ο\"},\n{\"Oopf\", \"𝕆\", NULL},\n{\"OpenCurlyDoubleQuote\", \"“\", NULL},\n{\"OpenCurlyQuote\", \"‘\", NULL},\n{\"Or\", \"⩔\", NULL},\n{\"Oscr\", \"𝒪\", NULL},\n{\"Oslash\", \"Ø\", \"Ø\"},\n{\"Otilde\", \"Õ\", \"Õ\"},\n{\"Otimes\", \"⨷\", NULL},\n{\"Ouml\", \"Ö\", \"Ö\"},\n{\"OverBar\", \"‾\", NULL},\n{\"OverBrace\", \"⏞\", NULL},\n{\"OverBracket\", \"⎴\", NULL},\n{\"OverParenthesis\", \"⏜\", NULL},\n{\"PartialD\", \"∂\", NULL},\n{\"Pcy\", \"П\", NULL},\n{\"Pfr\", \"𝔓\", NULL},\n{\"Phi\", \"Φ\", \"Φ\"},\n{\"Pi\", \"Π\", \"Π\"},\n{\"PlusMinus\", \"±\", NULL},\n{\"Poincareplane\", \"ℌ\", NULL},\n{\"Popf\", \"ℙ\", NULL},\n{\"Pr\", \"⪻\", NULL},\n{\"Precedes\", \"≺\", NULL},\n{\"PrecedesEqual\", \"⪯\", NULL},\n{\"PrecedesSlantEqual\", \"≼\", NULL},\n{\"PrecedesTilde\", \"≾\", NULL},\n{\"Prime\", \"″\", \"″\"},\n{\"Product\", \"∏\", NULL},\n{\"Proportion\", \"∷\", NULL},\n{\"Proportional\", \"∝\", NULL},\n{\"Pscr\", \"𝒫\", NULL},\n{\"Psi\", \"Ψ\", \"Ψ\"},\n{\"QUOT\", \"\\\"\", NULL},\n{\"Qfr\", \"𝔔\", NULL},\n{\"Qopf\", \"ℚ\", NULL},\n{\"Qscr\", \"𝒬\", NULL},\n{\"RBarr\", \"⤐\", NULL},\n{\"REG\", \"®\", NULL},\n{\"Racute\", \"Ŕ\", NULL},\n{\"Rang\", \"⟫\", NULL},\n{\"Rarr\", \"↠\", NULL},\n{\"Rarrtl\", \"⤖\", NULL},\n{\"Rcaron\", \"Ř\", NULL},\n{\"Rcedil\", \"Ŗ\", NULL},\n{\"Rcy\", \"Р\", NULL},\n{\"Re\", \"ℜ\", NULL},\n{\"ReverseElement\", \"∋\", NULL},\n{\"ReverseEquilibrium\", \"⇋\", NULL},\n{\"ReverseUpEquilibrium\", \"⥯\", NULL},\n{\"Rfr\", \"ℜ\", NULL},\n{\"Rho\", \"Ρ\", \"Ρ\"},\n{\"RightAngleBracket\", \"⟩\", NULL},\n{\"RightArrow\", \"→\", NULL},\n{\"RightArrowBar\", \"⇥\", NULL},\n{\"RightArrowLeftArrow\", \"⇄\", NULL},\n{\"RightCeiling\", \"⌉\", NULL},\n{\"RightDoubleBracket\", \"⟧\", NULL},\n{\"RightDownTeeVector\", \"⥝\", NULL},\n{\"RightDownVector\", \"⇂\", NULL},\n{\"RightDownVectorBar\", \"⥕\", NULL},\n{\"RightFloor\", \"⌋\", NULL},\n{\"RightTee\", \"⊢\", NULL},\n{\"RightTeeArrow\", \"↦\", NULL},\n{\"RightTeeVector\", \"⥛\", NULL},\n{\"RightTriangle\", \"⊳\", NULL},\n{\"RightTriangleBar\", \"⧐\", NULL},\n{\"RightTriangleEqual\", \"⊵\", NULL},\n{\"RightUpDownVector\", \"⥏\", NULL},\n{\"RightUpTeeVector\", \"⥜\", NULL},\n{\"RightUpVector\", \"↾\", NULL},\n{\"RightUpVectorBar\", \"⥔\", NULL},\n{\"RightVector\", \"⇀\", NULL},\n{\"RightVectorBar\", \"⥓\", NULL},\n{\"Rightarrow\", \"⇒\", NULL},\n{\"Ropf\", \"ℝ\", NULL},\n{\"RoundImplies\", \"⥰\", NULL},\n{\"Rrightarrow\", \"⇛\", NULL},\n{\"Rscr\", \"ℛ\", NULL},\n{\"Rsh\", \"↱\", NULL},\n{\"RuleDelayed\", \"⧴\", NULL},\n{\"SHCHcy\", \"Щ\", NULL},\n{\"SHcy\", \"Ш\", NULL},\n{\"SOFTcy\", \"Ь\", NULL},\n{\"Sacute\", \"Ś\", NULL},\n{\"Sc\", \"⪼\", NULL},\n{\"Scaron\", \"Š\", \"Š\"},\n{\"Scedil\", \"Ş\", NULL},\n{\"Scirc\", \"Ŝ\", NULL},\n{\"Scy\", \"С\", NULL},\n{\"Sfr\", \"𝔖\", NULL},\n{\"ShortDownArrow\", \"↓\", NULL},\n{\"ShortLeftArrow\", \"←\", NULL},\n{\"ShortRightArrow\", \"→\", NULL},\n{\"ShortUpArrow\", \"↑\", NULL},\n{\"Sigma\", \"Σ\", \"Σ\"},\n{\"SmallCircle\", \"∘\", NULL},\n{\"Sopf\", \"𝕊\", NULL},\n{\"Sqrt\", \"√\", NULL},\n{\"Square\", \"□\", NULL},\n{\"SquareIntersection\", \"⊓\", NULL},\n{\"SquareSubset\", \"⊏\", NULL},\n{\"SquareSubsetEqual\", \"⊑\", NULL},\n{\"SquareSuperset\", \"⊐\", NULL},\n{\"SquareSupersetEqual\", \"⊒\", NULL},\n{\"SquareUnion\", \"⊔\", NULL},\n{\"Sscr\", \"𝒮\", NULL},\n{\"Star\", \"⋆\", NULL},\n{\"Sub\", \"⋐\", NULL},\n{\"Subset\", \"⋐\", NULL},\n{\"SubsetEqual\", \"⊆\", NULL},\n{\"Succeeds\", \"≻\", NULL},\n{\"SucceedsEqual\", \"⪰\", NULL},\n{\"SucceedsSlantEqual\", \"≽\", NULL},\n{\"SucceedsTilde\", \"≿\", NULL},\n{\"SuchThat\", \"∋\", NULL},\n{\"Sum\", \"∑\", NULL},\n{\"Sup\", \"⋑\", NULL},\n{\"Superset\", \"⊃\", NULL},\n{\"SupersetEqual\", \"⊇\", NULL},\n{\"Supset\", \"⋑\", NULL},\n{\"THORN\", \"Þ\", \"Þ\"},\n{\"TRADE\", \"™\", NULL},\n{\"TSHcy\", \"Ћ\", NULL},\n{\"TScy\", \"Ц\", NULL},\n{\"Tab\", \"\\t\", NULL},\n{\"Tau\", \"Τ\", \"Τ\"},\n{\"Tcaron\", \"Ť\", NULL},\n{\"Tcedil\", \"Ţ\", NULL},\n{\"Tcy\", \"Т\", NULL},\n{\"Tfr\", \"𝔗\", NULL},\n{\"Therefore\", \"∴\", NULL},\n{\"Theta\", \"Θ\", \"Θ\"},\n{\"ThickSpace\", \"  \", NULL},\n{\"ThinSpace\", \" \", NULL},\n{\"Tilde\", \"∼\", NULL},\n{\"TildeEqual\", \"≃\", NULL},\n{\"TildeFullEqual\", \"≅\", NULL},\n{\"TildeTilde\", \"≈\", NULL},\n{\"Topf\", \"𝕋\", NULL},\n{\"TripleDot\", \"⃛\", NULL},\n{\"Tscr\", \"𝒯\", NULL},\n{\"Tstrok\", \"Ŧ\", NULL},\n{\"Uacute\", \"Ú\", \"Ú\"},\n{\"Uarr\", \"↟\", NULL},\n{\"Uarrocir\", \"⥉\", NULL},\n{\"Ubrcy\", \"Ў\", NULL},\n{\"Ubreve\", \"Ŭ\", NULL},\n{\"Ucirc\", \"Û\", \"Û\"},\n{\"Ucy\", \"У\", NULL},\n{\"Udblac\", \"Ű\", NULL},\n{\"Ufr\", \"𝔘\", NULL},\n{\"Ugrave\", \"Ù\", \"Ù\"},\n{\"Umacr\", \"Ū\", NULL},\n{\"UnderBar\", \"_\", NULL},\n{\"UnderBrace\", \"⏟\", NULL},\n{\"UnderBracket\", \"⎵\", NULL},\n{\"UnderParenthesis\", \"⏝\", NULL},\n{\"Union\", \"⋃\", NULL},\n{\"UnionPlus\", \"⊎\", NULL},\n{\"Uogon\", \"Ų\", NULL},\n{\"Uopf\", \"𝕌\", NULL},\n{\"UpArrow\", \"↑\", NULL},\n{\"UpArrowBar\", \"⤒\", NULL},\n{\"UpArrowDownArrow\", \"⇅\", NULL},\n{\"UpDownArrow\", \"↕\", NULL},\n{\"UpEquilibrium\", \"⥮\", NULL},\n{\"UpTee\", \"⊥\", NULL},\n{\"UpTeeArrow\", \"↥\", NULL},\n{\"Uparrow\", \"⇑\", NULL},\n{\"Updownarrow\", \"⇕\", NULL},\n{\"UpperLeftArrow\", \"↖\", NULL},\n{\"UpperRightArrow\", \"↗\", NULL},\n{\"Upsi\", \"ϒ\", NULL},\n{\"Upsilon\", \"Υ\", \"Υ\"},\n{\"Uring\", \"Ů\", NULL},\n{\"Uscr\", \"𝒰\", NULL},\n{\"Utilde\", \"Ũ\", NULL},\n{\"Uuml\", \"Ü\", \"Ü\"},\n{\"VDash\", \"⊫\", NULL},\n{\"Vbar\", \"⫫\", NULL},\n{\"Vcy\", \"В\", NULL},\n{\"Vdash\", \"⊩\", NULL},\n{\"Vdashl\", \"⫦\", NULL},\n{\"Vee\", \"⋁\", NULL},\n{\"Verbar\", \"‖\", NULL},\n{\"Vert\", \"‖\", NULL},\n{\"VerticalBar\", \"∣\", NULL},\n{\"VerticalLine\", \"|\", NULL},\n{\"VerticalSeparator\", \"❘\", NULL},\n{\"VerticalTilde\", \"≀\", NULL},\n{\"VeryThinSpace\", \" \", NULL},\n{\"Vfr\", \"𝔙\", NULL},\n{\"Vopf\", \"𝕍\", NULL},\n{\"Vscr\", \"𝒱\", NULL},\n{\"Vvdash\", \"⊪\", NULL},\n{\"Wcirc\", \"Ŵ\", NULL},\n{\"Wedge\", \"⋀\", NULL},\n{\"Wfr\", \"𝔚\", NULL},\n{\"Wopf\", \"𝕎\", NULL},\n{\"Wscr\", \"𝒲\", NULL},\n{\"Xfr\", \"𝔛\", NULL},\n{\"Xi\", \"Ξ\", \"Ξ\"},\n{\"Xopf\", \"𝕏\", NULL},\n{\"Xscr\", \"𝒳\", NULL},\n{\"YAcy\", \"Я\", NULL},\n{\"YIcy\", \"Ї\", NULL},\n{\"YUcy\", \"Ю\", NULL},\n{\"Yacute\", \"Ý\", \"Ý\"},\n{\"Ycirc\", \"Ŷ\", NULL},\n{\"Ycy\", \"Ы\", NULL},\n{\"Yfr\", \"𝔜\", NULL},\n{\"Yopf\", \"𝕐\", NULL},\n{\"Yscr\", \"𝒴\", NULL},\n{\"Yuml\", \"Ÿ\", \"Ÿ\"},\n{\"ZHcy\", \"Ж\", NULL},\n{\"Zacute\", \"Ź\", NULL},\n{\"Zcaron\", \"Ž\", NULL},\n{\"Zcy\", \"З\", NULL},\n{\"Zdot\", \"Ż\", NULL},\n{\"ZeroWidthSpace\", \"​\", NULL},\n{\"Zeta\", \"Ζ\", \"Ζ\"},\n{\"Zfr\", \"ℨ\", NULL},\n{\"Zopf\", \"ℤ\", NULL},\n{\"Zscr\", \"𝒵\", NULL},\n{\"aacute\", \"á\", \"á\"},\n{\"abreve\", \"ă\", NULL},\n{\"ac\", \"∾\", NULL},\n{\"acE\", \"∾̳\", NULL},\n{\"acd\", \"∿\", NULL},\n{\"acirc\", \"â\", \"â\"},\n{\"acute\", \"´\", \"´\"},\n{\"acy\", \"а\", NULL},\n{\"aelig\", \"æ\", \"æ\"},\n{\"af\", \"⁡\", NULL},\n{\"afr\", \"𝔞\", NULL},\n{\"agrave\", \"à\", \"à\"},\n{\"alefsym\", \"ℵ\", \"ℵ\"},\n{\"aleph\", \"ℵ\", NULL},\n{\"alpha\", \"α\", \"α\"},\n{\"amacr\", \"ā\", NULL},\n{\"amalg\", \"⨿\", NULL},\n{\"amp\", \"&\", \"&\"},\n{\"and\", \"∧\", \"∧\"},\n{\"andand\", \"⩕\", NULL},\n{\"andd\", \"⩜\", NULL},\n{\"andslope\", \"⩘\", NULL},\n{\"andv\", \"⩚\", NULL},\n{\"ang\", \"∠\", \"∠\"},\n{\"ange\", \"⦤\", NULL},\n{\"angle\", \"∠\", NULL},\n{\"angmsd\", \"∡\", NULL},\n{\"angmsdaa\", \"⦨\", NULL},\n{\"angmsdab\", \"⦩\", NULL},\n{\"angmsdac\", \"⦪\", NULL},\n{\"angmsdad\", \"⦫\", NULL},\n{\"angmsdae\", \"⦬\", NULL},\n{\"angmsdaf\", \"⦭\", NULL},\n{\"angmsdag\", \"⦮\", NULL},\n{\"angmsdah\", \"⦯\", NULL},\n{\"angrt\", \"∟\", NULL},\n{\"angrtvb\", \"⊾\", NULL},\n{\"angrtvbd\", \"⦝\", NULL},\n{\"angsph\", \"∢\", NULL},\n{\"angst\", \"Å\", NULL},\n{\"angzarr\", \"⍼\", NULL},\n{\"aogon\", \"ą\", NULL},\n{\"aopf\", \"𝕒\", NULL},\n{\"ap\", \"≈\", NULL},\n{\"apE\", \"⩰\", NULL},\n{\"apacir\", \"⩯\", NULL},\n{\"ape\", \"≊\", NULL},\n{\"apid\", \"≋\", NULL},\n{\"apos\", \"'\", NULL},\n{\"approx\", \"≈\", NULL},\n{\"approxeq\", \"≊\", NULL},\n{\"aring\", \"å\", \"å\"},\n{\"ascr\", \"𝒶\", NULL},\n{\"ast\", \"*\", NULL},\n{\"asymp\", \"≈\", \"≈\"},\n{\"asympeq\", \"≍\", NULL},\n{\"atilde\", \"ã\", \"ã\"},\n{\"auml\", \"ä\", \"ä\"},\n{\"awconint\", \"∳\", NULL},\n{\"awint\", \"⨑\", NULL},\n{\"bNot\", \"⫭\", NULL},\n{\"backcong\", \"≌\", NULL},\n{\"backepsilon\", \"϶\", NULL},\n{\"backprime\", \"‵\", NULL},\n{\"backsim\", \"∽\", NULL},\n{\"backsimeq\", \"⋍\", NULL},\n{\"barvee\", \"⊽\", NULL},\n{\"barwed\", \"⌅\", NULL},\n{\"barwedge\", \"⌅\", NULL},\n{\"bbrk\", \"⎵\", NULL},\n{\"bbrktbrk\", \"⎶\", NULL},\n{\"bcong\", \"≌\", NULL},\n{\"bcy\", \"б\", NULL},\n{\"bdquo\", \"„\", \"„\"},\n{\"becaus\", \"∵\", NULL},\n{\"because\", \"∵\", NULL},\n{\"bemptyv\", \"⦰\", NULL},\n{\"bepsi\", \"϶\", NULL},\n{\"bernou\", \"ℬ\", NULL},\n{\"beta\", \"β\", \"β\"},\n{\"beth\", \"ℶ\", NULL},\n{\"between\", \"≬\", NULL},\n{\"bfr\", \"𝔟\", NULL},\n{\"bigcap\", \"⋂\", NULL},\n{\"bigcirc\", \"◯\", NULL},\n{\"bigcup\", \"⋃\", NULL},\n{\"bigodot\", \"⨀\", NULL},\n{\"bigoplus\", \"⨁\", NULL},\n{\"bigotimes\", \"⨂\", NULL},\n{\"bigsqcup\", \"⨆\", NULL},\n{\"bigstar\", \"★\", NULL},\n{\"bigtriangledown\", \"▽\", NULL},\n{\"bigtriangleup\", \"△\", NULL},\n{\"biguplus\", \"⨄\", NULL},\n{\"bigvee\", \"⋁\", NULL},\n{\"bigwedge\", \"⋀\", NULL},\n{\"bkarow\", \"⤍\", NULL},\n{\"blacklozenge\", \"⧫\", NULL},\n{\"blacksquare\", \"▪\", NULL},\n{\"blacktriangle\", \"▴\", NULL},\n{\"blacktriangledown\", \"▾\", NULL},\n{\"blacktriangleleft\", \"◂\", NULL},\n{\"blacktriangleright\", \"▸\", NULL},\n{\"blank\", \"␣\", NULL},\n{\"blk12\", \"▒\", NULL},\n{\"blk14\", \"░\", NULL},\n{\"blk34\", \"▓\", NULL},\n{\"block\", \"█\", NULL},\n{\"bne\", \"=⃥\", NULL},\n{\"bnequiv\", \"≡⃥\", NULL},\n{\"bnot\", \"⌐\", NULL},\n{\"bopf\", \"𝕓\", NULL},\n{\"bot\", \"⊥\", NULL},\n{\"bottom\", \"⊥\", NULL},\n{\"bowtie\", \"⋈\", NULL},\n{\"boxDL\", \"╗\", NULL},\n{\"boxDR\", \"╔\", NULL},\n{\"boxDl\", \"╖\", NULL},\n{\"boxDr\", \"╓\", NULL},\n{\"boxH\", \"═\", NULL},\n{\"boxHD\", \"╦\", NULL},\n{\"boxHU\", \"╩\", NULL},\n{\"boxHd\", \"╤\", NULL},\n{\"boxHu\", \"╧\", NULL},\n{\"boxUL\", \"╝\", NULL},\n{\"boxUR\", \"╚\", NULL},\n{\"boxUl\", \"╜\", NULL},\n{\"boxUr\", \"╙\", NULL},\n{\"boxV\", \"║\", NULL},\n{\"boxVH\", \"╬\", NULL},\n{\"boxVL\", \"╣\", NULL},\n{\"boxVR\", \"╠\", NULL},\n{\"boxVh\", \"╫\", NULL},\n{\"boxVl\", \"╢\", NULL},\n{\"boxVr\", \"╟\", NULL},\n{\"boxbox\", \"⧉\", NULL},\n{\"boxdL\", \"╕\", NULL},\n{\"boxdR\", \"╒\", NULL},\n{\"boxdl\", \"┐\", NULL},\n{\"boxdr\", \"┌\", NULL},\n{\"boxh\", \"─\", NULL},\n{\"boxhD\", \"╥\", NULL},\n{\"boxhU\", \"╨\", NULL},\n{\"boxhd\", \"┬\", NULL},\n{\"boxhu\", \"┴\", NULL},\n{\"boxminus\", \"⊟\", NULL},\n{\"boxplus\", \"⊞\", NULL},\n{\"boxtimes\", \"⊠\", NULL},\n{\"boxuL\", \"╛\", NULL},\n{\"boxuR\", \"╘\", NULL},\n{\"boxul\", \"┘\", NULL},\n{\"boxur\", \"└\", NULL},\n{\"boxv\", \"│\", NULL},\n{\"boxvH\", \"╪\", NULL},\n{\"boxvL\", \"╡\", NULL},\n{\"boxvR\", \"╞\", NULL},\n{\"boxvh\", \"┼\", NULL},\n{\"boxvl\", \"┤\", NULL},\n{\"boxvr\", \"├\", NULL},\n{\"bprime\", \"‵\", NULL},\n{\"breve\", \"˘\", NULL},\n{\"brvbar\", \"¦\", \"¦\"},\n{\"bscr\", \"𝒷\", NULL},\n{\"bsemi\", \"⁏\", NULL},\n{\"bsim\", \"∽\", NULL},\n{\"bsime\", \"⋍\", NULL},\n{\"bsol\", \"\\\\\", NULL},\n{\"bsolb\", \"⧅\", NULL},\n{\"bsolhsub\", \"⟈\", NULL},\n{\"bull\", \"•\", \"•\"},\n{\"bullet\", \"•\", NULL},\n{\"bump\", \"≎\", NULL},\n{\"bumpE\", \"⪮\", NULL},\n{\"bumpe\", \"≏\", NULL},\n{\"bumpeq\", \"≏\", NULL},\n{\"cacute\", \"ć\", NULL},\n{\"cap\", \"∩\", \"∩\"},\n{\"capand\", \"⩄\", NULL},\n{\"capbrcup\", \"⩉\", NULL},\n{\"capcap\", \"⩋\", NULL},\n{\"capcup\", \"⩇\", NULL},\n{\"capdot\", \"⩀\", NULL},\n{\"caps\", \"∩︀\", NULL},\n{\"caret\", \"⁁\", NULL},\n{\"caron\", \"ˇ\", NULL},\n{\"ccaps\", \"⩍\", NULL},\n{\"ccaron\", \"č\", NULL},\n{\"ccedil\", \"ç\", \"ç\"},\n{\"ccirc\", \"ĉ\", NULL},\n{\"ccups\", \"⩌\", NULL},\n{\"ccupssm\", \"⩐\", NULL},\n{\"cdot\", \"ċ\", NULL},\n{\"cedil\", \"¸\", \"¸\"},\n{\"cemptyv\", \"⦲\", NULL},\n{\"cent\", \"¢\", \"¢\"},\n{\"centerdot\", \"·\", NULL},\n{\"cfr\", \"𝔠\", NULL},\n{\"chcy\", \"ч\", NULL},\n{\"check\", \"✓\", NULL},\n{\"checkmark\", \"✓\", NULL},\n{\"chi\", \"χ\", \"χ\"},\n{\"cir\", \"○\", NULL},\n{\"cirE\", \"⧃\", NULL},\n{\"circ\", \"ˆ\", \"ˆ\"},\n{\"circeq\", \"≗\", NULL},\n{\"circlearrowleft\", \"↺\", NULL},\n{\"circlearrowright\", \"↻\", NULL},\n{\"circledR\", \"®\", NULL},\n{\"circledS\", \"Ⓢ\", NULL},\n{\"circledast\", \"⊛\", NULL},\n{\"circledcirc\", \"⊚\", NULL},\n{\"circleddash\", \"⊝\", NULL},\n{\"cire\", \"≗\", NULL},\n{\"cirfnint\", \"⨐\", NULL},\n{\"cirmid\", \"⫯\", NULL},\n{\"cirscir\", \"⧂\", NULL},\n{\"clubs\", \"♣\", \"♣\"},\n{\"clubsuit\", \"♣\", NULL},\n{\"colon\", \":\", NULL},\n{\"colone\", \"≔\", NULL},\n{\"coloneq\", \"≔\", NULL},\n{\"comma\", \",\", NULL},\n{\"commat\", \"@\", NULL},\n{\"comp\", \"∁\", NULL},\n{\"compfn\", \"∘\", NULL},\n{\"complement\", \"∁\", NULL},\n{\"complexes\", \"ℂ\", NULL},\n{\"cong\", \"≅\", \"≅\"},\n{\"congdot\", \"⩭\", NULL},\n{\"conint\", \"∮\", NULL},\n{\"copf\", \"𝕔\", NULL},\n{\"coprod\", \"∐\", NULL},\n{\"copy\", \"©\", \"©\"},\n{\"copysr\", \"℗\", NULL},\n{\"crarr\", \"↵\", \"↵\"},\n{\"cross\", \"✗\", NULL},\n{\"cscr\", \"𝒸\", NULL},\n{\"csub\", \"⫏\", NULL},\n{\"csube\", \"⫑\", NULL},\n{\"csup\", \"⫐\", NULL},\n{\"csupe\", \"⫒\", NULL},\n{\"ctdot\", \"⋯\", NULL},\n{\"cudarrl\", \"⤸\", NULL},\n{\"cudarrr\", \"⤵\", NULL},\n{\"cuepr\", \"⋞\", NULL},\n{\"cuesc\", \"⋟\", NULL},\n{\"cularr\", \"↶\", NULL},\n{\"cularrp\", \"⤽\", NULL},\n{\"cup\", \"∪\", \"∪\"},\n{\"cupbrcap\", \"⩈\", NULL},\n{\"cupcap\", \"⩆\", NULL},\n{\"cupcup\", \"⩊\", NULL},\n{\"cupdot\", \"⊍\", NULL},\n{\"cupor\", \"⩅\", NULL},\n{\"cups\", \"∪︀\", NULL},\n{\"curarr\", \"↷\", NULL},\n{\"curarrm\", \"⤼\", NULL},\n{\"curlyeqprec\", \"⋞\", NULL},\n{\"curlyeqsucc\", \"⋟\", NULL},\n{\"curlyvee\", \"⋎\", NULL},\n{\"curlywedge\", \"⋏\", NULL},\n{\"curren\", \"¤\", \"¤\"},\n{\"curvearrowleft\", \"↶\", NULL},\n{\"curvearrowright\", \"↷\", NULL},\n{\"cuvee\", \"⋎\", NULL},\n{\"cuwed\", \"⋏\", NULL},\n{\"cwconint\", \"∲\", NULL},\n{\"cwint\", \"∱\", NULL},\n{\"cylcty\", \"⌭\", NULL},\n{\"dArr\", \"⇓\", \"⇓\"},\n{\"dHar\", \"⥥\", NULL},\n{\"dagger\", \"†\", \"†\"},\n{\"daleth\", \"ℸ\", NULL},\n{\"darr\", \"↓\", \"↓\"},\n{\"dash\", \"‐\", NULL},\n{\"dashv\", \"⊣\", NULL},\n{\"dbkarow\", \"⤏\", NULL},\n{\"dblac\", \"˝\", NULL},\n{\"dcaron\", \"ď\", NULL},\n{\"dcy\", \"д\", NULL},\n{\"dd\", \"ⅆ\", NULL},\n{\"ddagger\", \"‡\", NULL},\n{\"ddarr\", \"⇊\", NULL},\n{\"ddotseq\", \"⩷\", NULL},\n{\"deg\", \"°\", \"°\"},\n{\"delta\", \"δ\", \"δ\"},\n{\"demptyv\", \"⦱\", NULL},\n{\"dfisht\", \"⥿\", NULL},\n{\"dfr\", \"𝔡\", NULL},\n{\"dharl\", \"⇃\", NULL},\n{\"dharr\", \"⇂\", NULL},\n{\"diam\", \"⋄\", NULL},\n{\"diamond\", \"⋄\", NULL},\n{\"diamondsuit\", \"♦\", NULL},\n{\"diams\", \"♦\", \"♦\"},\n{\"die\", \"¨\", NULL},\n{\"digamma\", \"ϝ\", NULL},\n{\"disin\", \"⋲\", NULL},\n{\"div\", \"÷\", NULL},\n{\"divide\", \"÷\", \"÷\"},\n{\"divideontimes\", \"⋇\", NULL},\n{\"divonx\", \"⋇\", NULL},\n{\"djcy\", \"ђ\", NULL},\n{\"dlcorn\", \"⌞\", NULL},\n{\"dlcrop\", \"⌍\", NULL},\n{\"dollar\", \"$\", NULL},\n{\"dopf\", \"𝕕\", NULL},\n{\"dot\", \"˙\", NULL},\n{\"doteq\", \"≐\", NULL},\n{\"doteqdot\", \"≑\", NULL},\n{\"dotminus\", \"∸\", NULL},\n{\"dotplus\", \"∔\", NULL},\n{\"dotsquare\", \"⊡\", NULL},\n{\"doublebarwedge\", \"⌆\", NULL},\n{\"downarrow\", \"↓\", NULL},\n{\"downdownarrows\", \"⇊\", NULL},\n{\"downharpoonleft\", \"⇃\", NULL},\n{\"downharpoonright\", \"⇂\", NULL},\n{\"drbkarow\", \"⤐\", NULL},\n{\"drcorn\", \"⌟\", NULL},\n{\"drcrop\", \"⌌\", NULL},\n{\"dscr\", \"𝒹\", NULL},\n{\"dscy\", \"ѕ\", NULL},\n{\"dsol\", \"⧶\", NULL},\n{\"dstrok\", \"đ\", NULL},\n{\"dtdot\", \"⋱\", NULL},\n{\"dtri\", \"▿\", NULL},\n{\"dtrif\", \"▾\", NULL},\n{\"duarr\", \"⇵\", NULL},\n{\"duhar\", \"⥯\", NULL},\n{\"dwangle\", \"⦦\", NULL},\n{\"dzcy\", \"џ\", NULL},\n{\"dzigrarr\", \"⟿\", NULL},\n{\"eDDot\", \"⩷\", NULL},\n{\"eDot\", \"≑\", NULL},\n{\"eacute\", \"é\", \"é\"},\n{\"easter\", \"⩮\", NULL},\n{\"ecaron\", \"ě\", NULL},\n{\"ecir\", \"≖\", NULL},\n{\"ecirc\", \"ê\", \"ê\"},\n{\"ecolon\", \"≕\", NULL},\n{\"ecy\", \"э\", NULL},\n{\"edot\", \"ė\", NULL},\n{\"ee\", \"ⅇ\", NULL},\n{\"efDot\", \"≒\", NULL},\n{\"efr\", \"𝔢\", NULL},\n{\"eg\", \"⪚\", NULL},\n{\"egrave\", \"è\", \"è\"},\n{\"egs\", \"⪖\", NULL},\n{\"egsdot\", \"⪘\", NULL},\n{\"el\", \"⪙\", NULL},\n{\"elinters\", \"⏧\", NULL},\n{\"ell\", \"ℓ\", NULL},\n{\"els\", \"⪕\", NULL},\n{\"elsdot\", \"⪗\", NULL},\n{\"emacr\", \"ē\", NULL},\n{\"empty\", \"∅\", \"∅\"},\n{\"emptyset\", \"∅\", NULL},\n{\"emptyv\", \"∅\", NULL},\n{\"emsp\", \" \", \" \"},\n{\"emsp13\", \" \", NULL},\n{\"emsp14\", \" \", NULL},\n{\"eng\", \"ŋ\", NULL},\n{\"ensp\", \" \", \" \"},\n{\"eogon\", \"ę\", NULL},\n{\"eopf\", \"𝕖\", NULL},\n{\"epar\", \"⋕\", NULL},\n{\"eparsl\", \"⧣\", NULL},\n{\"eplus\", \"⩱\", NULL},\n{\"epsi\", \"ε\", NULL},\n{\"epsilon\", \"ε\", \"ε\"},\n{\"epsiv\", \"ϵ\", NULL},\n{\"eqcirc\", \"≖\", NULL},\n{\"eqcolon\", \"≕\", NULL},\n{\"eqsim\", \"≂\", NULL},\n{\"eqslantgtr\", \"⪖\", NULL},\n{\"eqslantless\", \"⪕\", NULL},\n{\"equals\", \"=\", NULL},\n{\"equest\", \"≟\", NULL},\n{\"equiv\", \"≡\", \"≡\"},\n{\"equivDD\", \"⩸\", NULL},\n{\"eqvparsl\", \"⧥\", NULL},\n{\"erDot\", \"≓\", NULL},\n{\"erarr\", \"⥱\", NULL},\n{\"escr\", \"ℯ\", NULL},\n{\"esdot\", \"≐\", NULL},\n{\"esim\", \"≂\", NULL},\n{\"eta\", \"η\", \"η\"},\n{\"eth\", \"ð\", \"ð\"},\n{\"euml\", \"ë\", \"ë\"},\n{\"euro\", \"€\", \"€\"},\n{\"excl\", \"!\", NULL},\n{\"exist\", \"∃\", \"∃\"},\n{\"expectation\", \"ℰ\", NULL},\n{\"exponentiale\", \"ⅇ\", NULL},\n{\"fallingdotseq\", \"≒\", NULL},\n{\"fcy\", \"ф\", NULL},\n{\"female\", \"♀\", NULL},\n{\"ffilig\", \"ﬃ\", NULL},\n{\"fflig\", \"ﬀ\", NULL},\n{\"ffllig\", \"ﬄ\", NULL},\n{\"ffr\", \"𝔣\", NULL},\n{\"filig\", \"ﬁ\", NULL},\n{\"fjlig\", \"fj\", NULL},\n{\"flat\", \"♭\", NULL},\n{\"fllig\", \"ﬂ\", NULL},\n{\"fltns\", \"▱\", NULL},\n{\"fnof\", \"ƒ\", \"ƒ\"},\n{\"fopf\", \"𝕗\", NULL},\n{\"forall\", \"∀\", \"∀\"},\n{\"fork\", \"⋔\", NULL},\n{\"forkv\", \"⫙\", NULL},\n{\"fpartint\", \"⨍\", NULL},\n{\"frac12\", \"½\", \"½\"},\n{\"frac13\", \"⅓\", NULL},\n{\"frac14\", \"¼\", \"¼\"},\n{\"frac15\", \"⅕\", NULL},\n{\"frac16\", \"⅙\", NULL},\n{\"frac18\", \"⅛\", NULL},\n{\"frac23\", \"⅔\", NULL},\n{\"frac25\", \"⅖\", NULL},\n{\"frac34\", \"¾\", \"¾\"},\n{\"frac35\", \"⅗\", NULL},\n{\"frac38\", \"⅜\", NULL},\n{\"frac45\", \"⅘\", NULL},\n{\"frac56\", \"⅚\", NULL},\n{\"frac58\", \"⅝\", NULL},\n{\"frac78\", \"⅞\", NULL},\n{\"frasl\", \"⁄\", \"⁄\"},\n{\"frown\", \"⌢\", NULL},\n{\"fscr\", \"𝒻\", NULL},\n{\"gE\", \"≧\", NULL},\n{\"gEl\", \"⪌\", NULL},\n{\"gacute\", \"ǵ\", NULL},\n{\"gamma\", \"γ\", \"γ\"},\n{\"gammad\", \"ϝ\", NULL},\n{\"gap\", \"⪆\", NULL},\n{\"gbreve\", \"ğ\", NULL},\n{\"gcirc\", \"ĝ\", NULL},\n{\"gcy\", \"г\", NULL},\n{\"gdot\", \"ġ\", NULL},\n{\"ge\", \"≥\", \"≥\"},\n{\"gel\", \"⋛\", NULL},\n{\"geq\", \"≥\", NULL},\n{\"geqq\", \"≧\", NULL},\n{\"geqslant\", \"⩾\", NULL},\n{\"ges\", \"⩾\", NULL},\n{\"gescc\", \"⪩\", NULL},\n{\"gesdot\", \"⪀\", NULL},\n{\"gesdoto\", \"⪂\", NULL},\n{\"gesdotol\", \"⪄\", NULL},\n{\"gesl\", \"⋛︀\", NULL},\n{\"gesles\", \"⪔\", NULL},\n{\"gfr\", \"𝔤\", NULL},\n{\"gg\", \"≫\", NULL},\n{\"ggg\", \"⋙\", NULL},\n{\"gimel\", \"ℷ\", NULL},\n{\"gjcy\", \"ѓ\", NULL},\n{\"gl\", \"≷\", NULL},\n{\"glE\", \"⪒\", NULL},\n{\"gla\", \"⪥\", NULL},\n{\"glj\", \"⪤\", NULL},\n{\"gnE\", \"≩\", NULL},\n{\"gnap\", \"⪊\", NULL},\n{\"gnapprox\", \"⪊\", NULL},\n{\"gne\", \"⪈\", NULL},\n{\"gneq\", \"⪈\", NULL},\n{\"gneqq\", \"≩\", NULL},\n{\"gnsim\", \"⋧\", NULL},\n{\"gopf\", \"𝕘\", NULL},\n{\"grave\", \"`\", NULL},\n{\"gscr\", \"ℊ\", NULL},\n{\"gsim\", \"≳\", NULL},\n{\"gsime\", \"⪎\", NULL},\n{\"gsiml\", \"⪐\", NULL},\n{\"gt\", \">\", \">\"},\n{\"gtcc\", \"⪧\", NULL},\n{\"gtcir\", \"⩺\", NULL},\n{\"gtdot\", \"⋗\", NULL},\n{\"gtlPar\", \"⦕\", NULL},\n{\"gtquest\", \"⩼\", NULL},\n{\"gtrapprox\", \"⪆\", NULL},\n{\"gtrarr\", \"⥸\", NULL},\n{\"gtrdot\", \"⋗\", NULL},\n{\"gtreqless\", \"⋛\", NULL},\n{\"gtreqqless\", \"⪌\", NULL},\n{\"gtrless\", \"≷\", NULL},\n{\"gtrsim\", \"≳\", NULL},\n{\"gvertneqq\", \"≩︀\", NULL},\n{\"gvnE\", \"≩︀\", NULL},\n{\"hArr\", \"⇔\", \"⇔\"},\n{\"hairsp\", \" \", NULL},\n{\"half\", \"½\", NULL},\n{\"hamilt\", \"ℋ\", NULL},\n{\"hardcy\", \"ъ\", NULL},\n{\"harr\", \"↔\", \"↔\"},\n{\"harrcir\", \"⥈\", NULL},\n{\"harrw\", \"↭\", NULL},\n{\"hbar\", \"ℏ\", NULL},\n{\"hcirc\", \"ĥ\", NULL},\n{\"hearts\", \"♥\", \"♥\"},\n{\"heartsuit\", \"♥\", NULL},\n{\"hellip\", \"…\", \"…\"},\n{\"hercon\", \"⊹\", NULL},\n{\"hfr\", \"𝔥\", NULL},\n{\"hksearow\", \"⤥\", NULL},\n{\"hkswarow\", \"⤦\", NULL},\n{\"hoarr\", \"⇿\", NULL},\n{\"homtht\", \"∻\", NULL},\n{\"hookleftarrow\", \"↩\", NULL},\n{\"hookrightarrow\", \"↪\", NULL},\n{\"hopf\", \"𝕙\", NULL},\n{\"horbar\", \"―\", NULL},\n{\"hscr\", \"𝒽\", NULL},\n{\"hslash\", \"ℏ\", NULL},\n{\"hstrok\", \"ħ\", NULL},\n{\"hybull\", \"⁃\", NULL},\n{\"hyphen\", \"‐\", NULL},\n{\"iacute\", \"í\", \"í\"},\n{\"ic\", \"⁣\", NULL},\n{\"icirc\", \"î\", \"î\"},\n{\"icy\", \"и\", NULL},\n{\"iecy\", \"е\", NULL},\n{\"iexcl\", \"¡\", \"¡\"},\n{\"iff\", \"⇔\", NULL},\n{\"ifr\", \"𝔦\", NULL},\n{\"igrave\", \"ì\", \"ì\"},\n{\"ii\", \"ⅈ\", NULL},\n{\"iiiint\", \"⨌\", NULL},\n{\"iiint\", \"∭\", NULL},\n{\"iinfin\", \"⧜\", NULL},\n{\"iiota\", \"℩\", NULL},\n{\"ijlig\", \"ĳ\", NULL},\n{\"imacr\", \"ī\", NULL},\n{\"image\", \"ℑ\", \"ℑ\"},\n{\"imagline\", \"ℐ\", NULL},\n{\"imagpart\", \"ℑ\", NULL},\n{\"imath\", \"ı\", NULL},\n{\"imof\", \"⊷\", NULL},\n{\"imped\", \"Ƶ\", NULL},\n{\"in\", \"∈\", NULL},\n{\"incare\", \"℅\", NULL},\n{\"infin\", \"∞\", \"∞\"},\n{\"infintie\", \"⧝\", NULL},\n{\"inodot\", \"ı\", NULL},\n{\"int\", \"∫\", \"∫\"},\n{\"intcal\", \"⊺\", NULL},\n{\"integers\", \"ℤ\", NULL},\n{\"intercal\", \"⊺\", NULL},\n{\"intlarhk\", \"⨗\", NULL},\n{\"intprod\", \"⨼\", NULL},\n{\"iocy\", \"ё\", NULL},\n{\"iogon\", \"į\", NULL},\n{\"iopf\", \"𝕚\", NULL},\n{\"iota\", \"ι\", \"ι\"},\n{\"iprod\", \"⨼\", NULL},\n{\"iquest\", \"¿\", \"¿\"},\n{\"iscr\", \"𝒾\", NULL},\n{\"isin\", \"∈\", \"∈\"},\n{\"isinE\", \"⋹\", NULL},\n{\"isindot\", \"⋵\", NULL},\n{\"isins\", \"⋴\", NULL},\n{\"isinsv\", \"⋳\", NULL},\n{\"isinv\", \"∈\", NULL},\n{\"it\", \"⁢\", NULL},\n{\"itilde\", \"ĩ\", NULL},\n{\"iukcy\", \"і\", NULL},\n{\"iuml\", \"ï\", \"ï\"},\n{\"jcirc\", \"ĵ\", NULL},\n{\"jcy\", \"й\", NULL},\n{\"jfr\", \"𝔧\", NULL},\n{\"jmath\", \"ȷ\", NULL},\n{\"jopf\", \"𝕛\", NULL},\n{\"jscr\", \"𝒿\", NULL},\n{\"jsercy\", \"ј\", NULL},\n{\"jukcy\", \"є\", NULL},\n{\"kappa\", \"κ\", \"κ\"},\n{\"kappav\", \"ϰ\", NULL},\n{\"kcedil\", \"ķ\", NULL},\n{\"kcy\", \"к\", NULL},\n{\"kfr\", \"𝔨\", NULL},\n{\"kgreen\", \"ĸ\", NULL},\n{\"khcy\", \"х\", NULL},\n{\"kjcy\", \"ќ\", NULL},\n{\"kopf\", \"𝕜\", NULL},\n{\"kscr\", \"𝓀\", NULL},\n{\"lAarr\", \"⇚\", NULL},\n{\"lArr\", \"⇐\", \"⇐\"},\n{\"lAtail\", \"⤛\", NULL},\n{\"lBarr\", \"⤎\", NULL},\n{\"lE\", \"≦\", NULL},\n{\"lEg\", \"⪋\", NULL},\n{\"lHar\", \"⥢\", NULL},\n{\"lacute\", \"ĺ\", NULL},\n{\"laemptyv\", \"⦴\", NULL},\n{\"lagran\", \"ℒ\", NULL},\n{\"lambda\", \"λ\", \"λ\"},\n{\"lang\", \"⟨\", \"〈\"},\n{\"langd\", \"⦑\", NULL},\n{\"langle\", \"⟨\", NULL},\n{\"lap\", \"⪅\", NULL},\n{\"laquo\", \"«\", \"«\"},\n{\"larr\", \"←\", \"←\"},\n{\"larrb\", \"⇤\", NULL},\n{\"larrbfs\", \"⤟\", NULL},\n{\"larrfs\", \"⤝\", NULL},\n{\"larrhk\", \"↩\", NULL},\n{\"larrlp\", \"↫\", NULL},\n{\"larrpl\", \"⤹\", NULL},\n{\"larrsim\", \"⥳\", NULL},\n{\"larrtl\", \"↢\", NULL},\n{\"lat\", \"⪫\", NULL},\n{\"latail\", \"⤙\", NULL},\n{\"late\", \"⪭\", NULL},\n{\"lates\", \"⪭︀\", NULL},\n{\"lbarr\", \"⤌\", NULL},\n{\"lbbrk\", \"❲\", NULL},\n{\"lbrace\", \"{\", NULL},\n{\"lbrack\", \"[\", NULL},\n{\"lbrke\", \"⦋\", NULL},\n{\"lbrksld\", \"⦏\", NULL},\n{\"lbrkslu\", \"⦍\", NULL},\n{\"lcaron\", \"ľ\", NULL},\n{\"lcedil\", \"ļ\", NULL},\n{\"lceil\", \"⌈\", \"⌈\"},\n{\"lcub\", \"{\", NULL},\n{\"lcy\", \"л\", NULL},\n{\"ldca\", \"⤶\", NULL},\n{\"ldquo\", \"“\", \"“\"},\n{\"ldquor\", \"„\", NULL},\n{\"ldrdhar\", \"⥧\", NULL},\n{\"ldrushar\", \"⥋\", NULL},\n{\"ldsh\", \"↲\", NULL},\n{\"le\", \"≤\", \"≤\"},\n{\"leftarrow\", \"←\", NULL},\n{\"leftarrowtail\", \"↢\", NULL},\n{\"leftharpoondown\", \"↽\", NULL},\n{\"leftharpoonup\", \"↼\", NULL},\n{\"leftleftarrows\", \"⇇\", NULL},\n{\"leftrightarrow\", \"↔\", NULL},\n{\"leftrightarrows\", \"⇆\", NULL},\n{\"leftrightharpoons\", \"⇋\", NULL},\n{\"leftrightsquigarrow\", \"↭\", NULL},\n{\"leftthreetimes\", \"⋋\", NULL},\n{\"leg\", \"⋚\", NULL},\n{\"leq\", \"≤\", NULL},\n{\"leqq\", \"≦\", NULL},\n{\"leqslant\", \"⩽\", NULL},\n{\"les\", \"⩽\", NULL},\n{\"lescc\", \"⪨\", NULL},\n{\"lesdot\", \"⩿\", NULL},\n{\"lesdoto\", \"⪁\", NULL},\n{\"lesdotor\", \"⪃\", NULL},\n{\"lesg\", \"⋚︀\", NULL},\n{\"lesges\", \"⪓\", NULL},\n{\"lessapprox\", \"⪅\", NULL},\n{\"lessdot\", \"⋖\", NULL},\n{\"lesseqgtr\", \"⋚\", NULL},\n{\"lesseqqgtr\", \"⪋\", NULL},\n{\"lessgtr\", \"≶\", NULL},\n{\"lesssim\", \"≲\", NULL},\n{\"lfisht\", \"⥼\", NULL},\n{\"lfloor\", \"⌊\", \"⌊\"},\n{\"lfr\", \"𝔩\", NULL},\n{\"lg\", \"≶\", NULL},\n{\"lgE\", \"⪑\", NULL},\n{\"lhard\", \"↽\", NULL},\n{\"lharu\", \"↼\", NULL},\n{\"lharul\", \"⥪\", NULL},\n{\"lhblk\", \"▄\", NULL},\n{\"ljcy\", \"љ\", NULL},\n{\"ll\", \"≪\", NULL},\n{\"llarr\", \"⇇\", NULL},\n{\"llcorner\", \"⌞\", NULL},\n{\"llhard\", \"⥫\", NULL},\n{\"lltri\", \"◺\", NULL},\n{\"lmidot\", \"ŀ\", NULL},\n{\"lmoust\", \"⎰\", NULL},\n{\"lmoustache\", \"⎰\", NULL},\n{\"lnE\", \"≨\", NULL},\n{\"lnap\", \"⪉\", NULL},\n{\"lnapprox\", \"⪉\", NULL},\n{\"lne\", \"⪇\", NULL},\n{\"lneq\", \"⪇\", NULL},\n{\"lneqq\", \"≨\", NULL},\n{\"lnsim\", \"⋦\", NULL},\n{\"loang\", \"⟬\", NULL},\n{\"loarr\", \"⇽\", NULL},\n{\"lobrk\", \"⟦\", NULL},\n{\"longleftarrow\", \"⟵\", NULL},\n{\"longleftrightarrow\", \"⟷\", NULL},\n{\"longmapsto\", \"⟼\", NULL},\n{\"longrightarrow\", \"⟶\", NULL},\n{\"looparrowleft\", \"↫\", NULL},\n{\"looparrowright\", \"↬\", NULL},\n{\"lopar\", \"⦅\", NULL},\n{\"lopf\", \"𝕝\", NULL},\n{\"loplus\", \"⨭\", NULL},\n{\"lotimes\", \"⨴\", NULL},\n{\"lowast\", \"∗\", \"∗\"},\n{\"lowbar\", \"_\", NULL},\n{\"loz\", \"◊\", \"◊\"},\n{\"lozenge\", \"◊\", NULL},\n{\"lozf\", \"⧫\", NULL},\n{\"lpar\", \"(\", NULL},\n{\"lparlt\", \"⦓\", NULL},\n{\"lrarr\", \"⇆\", NULL},\n{\"lrcorner\", \"⌟\", NULL},\n{\"lrhar\", \"⇋\", NULL},\n{\"lrhard\", \"⥭\", NULL},\n{\"lrm\", \"‎\", \"‎\"},\n{\"lrtri\", \"⊿\", NULL},\n{\"lsaquo\", \"‹\", \"‹\"},\n{\"lscr\", \"𝓁\", NULL},\n{\"lsh\", \"↰\", NULL},\n{\"lsim\", \"≲\", NULL},\n{\"lsime\", \"⪍\", NULL},\n{\"lsimg\", \"⪏\", NULL},\n{\"lsqb\", \"[\", NULL},\n{\"lsquo\", \"‘\", \"‘\"},\n{\"lsquor\", \"‚\", NULL},\n{\"lstrok\", \"ł\", NULL},\n{\"lt\", \"<\", \"<\"},\n{\"ltcc\", \"⪦\", NULL},\n{\"ltcir\", \"⩹\", NULL},\n{\"ltdot\", \"⋖\", NULL},\n{\"lthree\", \"⋋\", NULL},\n{\"ltimes\", \"⋉\", NULL},\n{\"ltlarr\", \"⥶\", NULL},\n{\"ltquest\", \"⩻\", NULL},\n{\"ltrPar\", \"⦖\", NULL},\n{\"ltri\", \"◃\", NULL},\n{\"ltrie\", \"⊴\", NULL},\n{\"ltrif\", \"◂\", NULL},\n{\"lurdshar\", \"⥊\", NULL},\n{\"luruhar\", \"⥦\", NULL},\n{\"lvertneqq\", \"≨︀\", NULL},\n{\"lvnE\", \"≨︀\", NULL},\n{\"mDDot\", \"∺\", NULL},\n{\"macr\", \"¯\", \"¯\"},\n{\"male\", \"♂\", NULL},\n{\"malt\", \"✠\", NULL},\n{\"maltese\", \"✠\", NULL},\n{\"map\", \"↦\", NULL},\n{\"mapsto\", \"↦\", NULL},\n{\"mapstodown\", \"↧\", NULL},\n{\"mapstoleft\", \"↤\", NULL},\n{\"mapstoup\", \"↥\", NULL},\n{\"marker\", \"▮\", NULL},\n{\"mcomma\", \"⨩\", NULL},\n{\"mcy\", \"м\", NULL},\n{\"mdash\", \"—\", \"—\"},\n{\"measuredangle\", \"∡\", NULL},\n{\"mfr\", \"𝔪\", NULL},\n{\"mho\", \"℧\", NULL},\n{\"micro\", \"µ\", \"µ\"},\n{\"mid\", \"∣\", NULL},\n{\"midast\", \"*\", NULL},\n{\"midcir\", \"⫰\", NULL},\n{\"middot\", \"·\", \"·\"},\n{\"minus\", \"−\", \"−\"},\n{\"minusb\", \"⊟\", NULL},\n{\"minusd\", \"∸\", NULL},\n{\"minusdu\", \"⨪\", NULL},\n{\"mlcp\", \"⫛\", NULL},\n{\"mldr\", \"…\", NULL},\n{\"mnplus\", \"∓\", NULL},\n{\"models\", \"⊧\", NULL},\n{\"mopf\", \"𝕞\", NULL},\n{\"mp\", \"∓\", NULL},\n{\"mscr\", \"𝓂\", NULL},\n{\"mstpos\", \"∾\", NULL},\n{\"mu\", \"μ\", \"μ\"},\n{\"multimap\", \"⊸\", NULL},\n{\"mumap\", \"⊸\", NULL},\n{\"nGg\", \"⋙̸\", NULL},\n{\"nGt\", \"≫⃒\", NULL},\n{\"nGtv\", \"≫̸\", NULL},\n{\"nLeftarrow\", \"⇍\", NULL},\n{\"nLeftrightarrow\", \"⇎\", NULL},\n{\"nLl\", \"⋘̸\", NULL},\n{\"nLt\", \"≪⃒\", NULL},\n{\"nLtv\", \"≪̸\", NULL},\n{\"nRightarrow\", \"⇏\", NULL},\n{\"nVDash\", \"⊯\", NULL},\n{\"nVdash\", \"⊮\", NULL},\n{\"nabla\", \"∇\", \"∇\"},\n{\"nacute\", \"ń\", NULL},\n{\"nang\", \"∠⃒\", NULL},\n{\"nap\", \"≉\", NULL},\n{\"napE\", \"⩰̸\", NULL},\n{\"napid\", \"≋̸\", NULL},\n{\"napos\", \"ŉ\", NULL},\n{\"napprox\", \"≉\", NULL},\n{\"natur\", \"♮\", NULL},\n{\"natural\", \"♮\", NULL},\n{\"naturals\", \"ℕ\", NULL},\n{\"nbsp\", \" \", \" \"},\n{\"nbump\", \"≎̸\", NULL},\n{\"nbumpe\", \"≏̸\", NULL},\n{\"ncap\", \"⩃\", NULL},\n{\"ncaron\", \"ň\", NULL},\n{\"ncedil\", \"ņ\", NULL},\n{\"ncong\", \"≇\", NULL},\n{\"ncongdot\", \"⩭̸\", NULL},\n{\"ncup\", \"⩂\", NULL},\n{\"ncy\", \"н\", NULL},\n{\"ndash\", \"–\", \"–\"},\n{\"ne\", \"≠\", \"≠\"},\n{\"neArr\", \"⇗\", NULL},\n{\"nearhk\", \"⤤\", NULL},\n{\"nearr\", \"↗\", NULL},\n{\"nearrow\", \"↗\", NULL},\n{\"nedot\", \"≐̸\", NULL},\n{\"nequiv\", \"≢\", NULL},\n{\"nesear\", \"⤨\", NULL},\n{\"nesim\", \"≂̸\", NULL},\n{\"nexist\", \"∄\", NULL},\n{\"nexists\", \"∄\", NULL},\n{\"nfr\", \"𝔫\", NULL},\n{\"ngE\", \"≧̸\", NULL},\n{\"nge\", \"≱\", NULL},\n{\"ngeq\", \"≱\", NULL},\n{\"ngeqq\", \"≧̸\", NULL},\n{\"ngeqslant\", \"⩾̸\", NULL},\n{\"nges\", \"⩾̸\", NULL},\n{\"ngsim\", \"≵\", NULL},\n{\"ngt\", \"≯\", NULL},\n{\"ngtr\", \"≯\", NULL},\n{\"nhArr\", \"⇎\", NULL},\n{\"nharr\", \"↮\", NULL},\n{\"nhpar\", \"⫲\", NULL},\n{\"ni\", \"∋\", \"∋\"},\n{\"nis\", \"⋼\", NULL},\n{\"nisd\", \"⋺\", NULL},\n{\"niv\", \"∋\", NULL},\n{\"njcy\", \"њ\", NULL},\n{\"nlArr\", \"⇍\", NULL},\n{\"nlE\", \"≦̸\", NULL},\n{\"nlarr\", \"↚\", NULL},\n{\"nldr\", \"‥\", NULL},\n{\"nle\", \"≰\", NULL},\n{\"nleftarrow\", \"↚\", NULL},\n{\"nleftrightarrow\", \"↮\", NULL},\n{\"nleq\", \"≰\", NULL},\n{\"nleqq\", \"≦̸\", NULL},\n{\"nleqslant\", \"⩽̸\", NULL},\n{\"nles\", \"⩽̸\", NULL},\n{\"nless\", \"≮\", NULL},\n{\"nlsim\", \"≴\", NULL},\n{\"nlt\", \"≮\", NULL},\n{\"nltri\", \"⋪\", NULL},\n{\"nltrie\", \"⋬\", NULL},\n{\"nmid\", \"∤\", NULL},\n{\"nopf\", \"𝕟\", NULL},\n{\"not\", \"¬\", \"¬\"},\n{\"notin\", \"∉\", \"∉\"},\n{\"notinE\", \"⋹̸\", NULL},\n{\"notindot\", \"⋵̸\", NULL},\n{\"notinva\", \"∉\", NULL},\n{\"notinvb\", \"⋷\", NULL},\n{\"notinvc\", \"⋶\", NULL},\n{\"notni\", \"∌\", NULL},\n{\"notniva\", \"∌\", NULL},\n{\"notnivb\", \"⋾\", NULL},\n{\"notnivc\", \"⋽\", NULL},\n{\"npar\", \"∦\", NULL},\n{\"nparallel\", \"∦\", NULL},\n{\"nparsl\", \"⫽⃥\", NULL},\n{\"npart\", \"∂̸\", NULL},\n{\"npolint\", \"⨔\", NULL},\n{\"npr\", \"⊀\", NULL},\n{\"nprcue\", \"⋠\", NULL},\n{\"npre\", \"⪯̸\", NULL},\n{\"nprec\", \"⊀\", NULL},\n{\"npreceq\", \"⪯̸\", NULL},\n{\"nrArr\", \"⇏\", NULL},\n{\"nrarr\", \"↛\", NULL},\n{\"nrarrc\", \"⤳̸\", NULL},\n{\"nrarrw\", \"↝̸\", NULL},\n{\"nrightarrow\", \"↛\", NULL},\n{\"nrtri\", \"⋫\", NULL},\n{\"nrtrie\", \"⋭\", NULL},\n{\"nsc\", \"⊁\", NULL},\n{\"nsccue\", \"⋡\", NULL},\n{\"nsce\", \"⪰̸\", NULL},\n{\"nscr\", \"𝓃\", NULL},\n{\"nshortmid\", \"∤\", NULL},\n{\"nshortparallel\", \"∦\", NULL},\n{\"nsim\", \"≁\", NULL},\n{\"nsime\", \"≄\", NULL},\n{\"nsimeq\", \"≄\", NULL},\n{\"nsmid\", \"∤\", NULL},\n{\"nspar\", \"∦\", NULL},\n{\"nsqsube\", \"⋢\", NULL},\n{\"nsqsupe\", \"⋣\", NULL},\n{\"nsub\", \"⊄\", \"⊄\"},\n{\"nsubE\", \"⫅̸\", NULL},\n{\"nsube\", \"⊈\", NULL},\n{\"nsubset\", \"⊂⃒\", NULL},\n{\"nsubseteq\", \"⊈\", NULL},\n{\"nsubseteqq\", \"⫅̸\", NULL},\n{\"nsucc\", \"⊁\", NULL},\n{\"nsucceq\", \"⪰̸\", NULL},\n{\"nsup\", \"⊅\", NULL},\n{\"nsupE\", \"⫆̸\", NULL},\n{\"nsupe\", \"⊉\", NULL},\n{\"nsupset\", \"⊃⃒\", NULL},\n{\"nsupseteq\", \"⊉\", NULL},\n{\"nsupseteqq\", \"⫆̸\", NULL},\n{\"ntgl\", \"≹\", NULL},\n{\"ntilde\", \"ñ\", \"ñ\"},\n{\"ntlg\", \"≸\", NULL},\n{\"ntriangleleft\", \"⋪\", NULL},\n{\"ntrianglelefteq\", \"⋬\", NULL},\n{\"ntriangleright\", \"⋫\", NULL},\n{\"ntrianglerighteq\", \"⋭\", NULL},\n{\"nu\", \"ν\", \"ν\"},\n{\"num\", \"#\", NULL},\n{\"numero\", \"№\", NULL},\n{\"numsp\", \" \", NULL},\n{\"nvDash\", \"⊭\", NULL},\n{\"nvHarr\", \"⤄\", NULL},\n{\"nvap\", \"≍⃒\", NULL},\n{\"nvdash\", \"⊬\", NULL},\n{\"nvge\", \"≥⃒\", NULL},\n{\"nvgt\", \">⃒\", NULL},\n{\"nvinfin\", \"⧞\", NULL},\n{\"nvlArr\", \"⤂\", NULL},\n{\"nvle\", \"≤⃒\", NULL},\n{\"nvlt\", \"<⃒\", NULL},\n{\"nvltrie\", \"⊴⃒\", NULL},\n{\"nvrArr\", \"⤃\", NULL},\n{\"nvrtrie\", \"⊵⃒\", NULL},\n{\"nvsim\", \"∼⃒\", NULL},\n{\"nwArr\", \"⇖\", NULL},\n{\"nwarhk\", \"⤣\", NULL},\n{\"nwarr\", \"↖\", NULL},\n{\"nwarrow\", \"↖\", NULL},\n{\"nwnear\", \"⤧\", NULL},\n{\"oS\", \"Ⓢ\", NULL},\n{\"oacute\", \"ó\", \"ó\"},\n{\"oast\", \"⊛\", NULL},\n{\"ocir\", \"⊚\", NULL},\n{\"ocirc\", \"ô\", \"ô\"},\n{\"ocy\", \"о\", NULL},\n{\"odash\", \"⊝\", NULL},\n{\"odblac\", \"ő\", NULL},\n{\"odiv\", \"⨸\", NULL},\n{\"odot\", \"⊙\", NULL},\n{\"odsold\", \"⦼\", NULL},\n{\"oelig\", \"œ\", \"œ\"},\n{\"ofcir\", \"⦿\", NULL},\n{\"ofr\", \"𝔬\", NULL},\n{\"ogon\", \"˛\", NULL},\n{\"ograve\", \"ò\", \"ò\"},\n{\"ogt\", \"⧁\", NULL},\n{\"ohbar\", \"⦵\", NULL},\n{\"ohm\", \"Ω\", NULL},\n{\"oint\", \"∮\", NULL},\n{\"olarr\", \"↺\", NULL},\n{\"olcir\", \"⦾\", NULL},\n{\"olcross\", \"⦻\", NULL},\n{\"oline\", \"‾\", \"‾\"},\n{\"olt\", \"⧀\", NULL},\n{\"omacr\", \"ō\", NULL},\n{\"omega\", \"ω\", \"ω\"},\n{\"omicron\", \"ο\", \"ο\"},\n{\"omid\", \"⦶\", NULL},\n{\"ominus\", \"⊖\", NULL},\n{\"oopf\", \"𝕠\", NULL},\n{\"opar\", \"⦷\", NULL},\n{\"operp\", \"⦹\", NULL},\n{\"oplus\", \"⊕\", \"⊕\"},\n{\"or\", \"∨\", \"∨\"},\n{\"orarr\", \"↻\", NULL},\n{\"ord\", \"⩝\", NULL},\n{\"order\", \"ℴ\", NULL},\n{\"orderof\", \"ℴ\", NULL},\n{\"ordf\", \"ª\", \"ª\"},\n{\"ordm\", \"º\", \"º\"},\n{\"origof\", \"⊶\", NULL},\n{\"oror\", \"⩖\", NULL},\n{\"orslope\", \"⩗\", NULL},\n{\"orv\", \"⩛\", NULL},\n{\"oscr\", \"ℴ\", NULL},\n{\"oslash\", \"ø\", \"ø\"},\n{\"osol\", \"⊘\", NULL},\n{\"otilde\", \"õ\", \"õ\"},\n{\"otimes\", \"⊗\", \"⊗\"},\n{\"otimesas\", \"⨶\", NULL},\n{\"ouml\", \"ö\", \"ö\"},\n{\"ovbar\", \"⌽\", NULL},\n{\"par\", \"∥\", NULL},\n{\"para\", \"¶\", \"¶\"},\n{\"parallel\", \"∥\", NULL},\n{\"parsim\", \"⫳\", NULL},\n{\"parsl\", \"⫽\", NULL},\n{\"part\", \"∂\", \"∂\"},\n{\"pcy\", \"п\", NULL},\n{\"percnt\", \"%\", NULL},\n{\"period\", \".\", NULL},\n{\"permil\", \"‰\", \"‰\"},\n{\"perp\", \"⊥\", \"⊥\"},\n{\"pertenk\", \"‱\", NULL},\n{\"pfr\", \"𝔭\", NULL},\n{\"phi\", \"φ\", \"φ\"},\n{\"phiv\", \"ϕ\", NULL},\n{\"phmmat\", \"ℳ\", NULL},\n{\"phone\", \"☎\", NULL},\n{\"pi\", \"π\", \"π\"},\n{\"pitchfork\", \"⋔\", NULL},\n{\"piv\", \"ϖ\", \"ϖ\"},\n{\"planck\", \"ℏ\", NULL},\n{\"planckh\", \"ℎ\", NULL},\n{\"plankv\", \"ℏ\", NULL},\n{\"plus\", \"+\", NULL},\n{\"plusacir\", \"⨣\", NULL},\n{\"plusb\", \"⊞\", NULL},\n{\"pluscir\", \"⨢\", NULL},\n{\"plusdo\", \"∔\", NULL},\n{\"plusdu\", \"⨥\", NULL},\n{\"pluse\", \"⩲\", NULL},\n{\"plusmn\", \"±\", \"±\"},\n{\"plussim\", \"⨦\", NULL},\n{\"plustwo\", \"⨧\", NULL},\n{\"pm\", \"±\", NULL},\n{\"pointint\", \"⨕\", NULL},\n{\"popf\", \"𝕡\", NULL},\n{\"pound\", \"£\", \"£\"},\n{\"pr\", \"≺\", NULL},\n{\"prE\", \"⪳\", NULL},\n{\"prap\", \"⪷\", NULL},\n{\"prcue\", \"≼\", NULL},\n{\"pre\", \"⪯\", NULL},\n{\"prec\", \"≺\", NULL},\n{\"precapprox\", \"⪷\", NULL},\n{\"preccurlyeq\", \"≼\", NULL},\n{\"preceq\", \"⪯\", NULL},\n{\"precnapprox\", \"⪹\", NULL},\n{\"precneqq\", \"⪵\", NULL},\n{\"precnsim\", \"⋨\", NULL},\n{\"precsim\", \"≾\", NULL},\n{\"prime\", \"′\", \"′\"},\n{\"primes\", \"ℙ\", NULL},\n{\"prnE\", \"⪵\", NULL},\n{\"prnap\", \"⪹\", NULL},\n{\"prnsim\", \"⋨\", NULL},\n{\"prod\", \"∏\", \"∏\"},\n{\"profalar\", \"⌮\", NULL},\n{\"profline\", \"⌒\", NULL},\n{\"profsurf\", \"⌓\", NULL},\n{\"prop\", \"∝\", \"∝\"},\n{\"propto\", \"∝\", NULL},\n{\"prsim\", \"≾\", NULL},\n{\"prurel\", \"⊰\", NULL},\n{\"pscr\", \"𝓅\", NULL},\n{\"psi\", \"ψ\", \"ψ\"},\n{\"puncsp\", \" \", NULL},\n{\"qfr\", \"𝔮\", NULL},\n{\"qint\", \"⨌\", NULL},\n{\"qopf\", \"𝕢\", NULL},\n{\"qprime\", \"⁗\", NULL},\n{\"qscr\", \"𝓆\", NULL},\n{\"quaternions\", \"ℍ\", NULL},\n{\"quatint\", \"⨖\", NULL},\n{\"quest\", \"?\", NULL},\n{\"questeq\", \"≟\", NULL},\n{\"quot\", \"\\\"\", \"\\\"\"},\n{\"rAarr\", \"⇛\", NULL},\n{\"rArr\", \"⇒\", \"⇒\"},\n{\"rAtail\", \"⤜\", NULL},\n{\"rBarr\", \"⤏\", NULL},\n{\"rHar\", \"⥤\", NULL},\n{\"race\", \"∽̱\", NULL},\n{\"racute\", \"ŕ\", NULL},\n{\"radic\", \"√\", \"√\"},\n{\"raemptyv\", \"⦳\", NULL},\n{\"rang\", \"⟩\", \"〉\"},\n{\"rangd\", \"⦒\", NULL},\n{\"range\", \"⦥\", NULL},\n{\"rangle\", \"⟩\", NULL},\n{\"raquo\", \"»\", \"»\"},\n{\"rarr\", \"→\", \"→\"},\n{\"rarrap\", \"⥵\", NULL},\n{\"rarrb\", \"⇥\", NULL},\n{\"rarrbfs\", \"⤠\", NULL},\n{\"rarrc\", \"⤳\", NULL},\n{\"rarrfs\", \"⤞\", NULL},\n{\"rarrhk\", \"↪\", NULL},\n{\"rarrlp\", \"↬\", NULL},\n{\"rarrpl\", \"⥅\", NULL},\n{\"rarrsim\", \"⥴\", NULL},\n{\"rarrtl\", \"↣\", NULL},\n{\"rarrw\", \"↝\", NULL},\n{\"ratail\", \"⤚\", NULL},\n{\"ratio\", \"∶\", NULL},\n{\"rationals\", \"ℚ\", NULL},\n{\"rbarr\", \"⤍\", NULL},\n{\"rbbrk\", \"❳\", NULL},\n{\"rbrace\", \"}\", NULL},\n{\"rbrack\", \"]\", NULL},\n{\"rbrke\", \"⦌\", NULL},\n{\"rbrksld\", \"⦎\", NULL},\n{\"rbrkslu\", \"⦐\", NULL},\n{\"rcaron\", \"ř\", NULL},\n{\"rcedil\", \"ŗ\", NULL},\n{\"rceil\", \"⌉\", \"⌉\"},\n{\"rcub\", \"}\", NULL},\n{\"rcy\", \"р\", NULL},\n{\"rdca\", \"⤷\", NULL},\n{\"rdldhar\", \"⥩\", NULL},\n{\"rdquo\", \"”\", \"”\"},\n{\"rdquor\", \"”\", NULL},\n{\"rdsh\", \"↳\", NULL},\n{\"real\", \"ℜ\", \"ℜ\"},\n{\"realine\", \"ℛ\", NULL},\n{\"realpart\", \"ℜ\", NULL},\n{\"reals\", \"ℝ\", NULL},\n{\"rect\", \"▭\", NULL},\n{\"reg\", \"®\", \"®\"},\n{\"rfisht\", \"⥽\", NULL},\n{\"rfloor\", \"⌋\", \"⌋\"},\n{\"rfr\", \"𝔯\", NULL},\n{\"rhard\", \"⇁\", NULL},\n{\"rharu\", \"⇀\", NULL},\n{\"rharul\", \"⥬\", NULL},\n{\"rho\", \"ρ\", \"ρ\"},\n{\"rhov\", \"ϱ\", NULL},\n{\"rightarrow\", \"→\", NULL},\n{\"rightarrowtail\", \"↣\", NULL},\n{\"rightharpoondown\", \"⇁\", NULL},\n{\"rightharpoonup\", \"⇀\", NULL},\n{\"rightleftarrows\", \"⇄\", NULL},\n{\"rightleftharpoons\", \"⇌\", NULL},\n{\"rightrightarrows\", \"⇉\", NULL},\n{\"rightsquigarrow\", \"↝\", NULL},\n{\"rightthreetimes\", \"⋌\", NULL},\n{\"ring\", \"˚\", NULL},\n{\"risingdotseq\", \"≓\", NULL},\n{\"rlarr\", \"⇄\", NULL},\n{\"rlhar\", \"⇌\", NULL},\n{\"rlm\", \"‏\", \"‏\"},\n{\"rmoust\", \"⎱\", NULL},\n{\"rmoustache\", \"⎱\", NULL},\n{\"rnmid\", \"⫮\", NULL},\n{\"roang\", \"⟭\", NULL},\n{\"roarr\", \"⇾\", NULL},\n{\"robrk\", \"⟧\", NULL},\n{\"ropar\", \"⦆\", NULL},\n{\"ropf\", \"𝕣\", NULL},\n{\"roplus\", \"⨮\", NULL},\n{\"rotimes\", \"⨵\", NULL},\n{\"rpar\", \")\", NULL},\n{\"rpargt\", \"⦔\", NULL},\n{\"rppolint\", \"⨒\", NULL},\n{\"rrarr\", \"⇉\", NULL},\n{\"rsaquo\", \"›\", \"›\"},\n{\"rscr\", \"𝓇\", NULL},\n{\"rsh\", \"↱\", NULL},\n{\"rsqb\", \"]\", NULL},\n{\"rsquo\", \"’\", \"’\"},\n{\"rsquor\", \"’\", NULL},\n{\"rthree\", \"⋌\", NULL},\n{\"rtimes\", \"⋊\", NULL},\n{\"rtri\", \"▹\", NULL},\n{\"rtrie\", \"⊵\", NULL},\n{\"rtrif\", \"▸\", NULL},\n{\"rtriltri\", \"⧎\", NULL},\n{\"ruluhar\", \"⥨\", NULL},\n{\"rx\", \"℞\", NULL},\n{\"sacute\", \"ś\", NULL},\n{\"sbquo\", \"‚\", \"‚\"},\n{\"sc\", \"≻\", NULL},\n{\"scE\", \"⪴\", NULL},\n{\"scap\", \"⪸\", NULL},\n{\"scaron\", \"š\", \"š\"},\n{\"sccue\", \"≽\", NULL},\n{\"sce\", \"⪰\", NULL},\n{\"scedil\", \"ş\", NULL},\n{\"scirc\", \"ŝ\", NULL},\n{\"scnE\", \"⪶\", NULL},\n{\"scnap\", \"⪺\", NULL},\n{\"scnsim\", \"⋩\", NULL},\n{\"scpolint\", \"⨓\", NULL},\n{\"scsim\", \"≿\", NULL},\n{\"scy\", \"с\", NULL},\n{\"sdot\", \"⋅\", \"⋅\"},\n{\"sdotb\", \"⊡\", NULL},\n{\"sdote\", \"⩦\", NULL},\n{\"seArr\", \"⇘\", NULL},\n{\"searhk\", \"⤥\", NULL},\n{\"searr\", \"↘\", NULL},\n{\"searrow\", \"↘\", NULL},\n{\"sect\", \"§\", \"§\"},\n{\"semi\", \";\", NULL},\n{\"seswar\", \"⤩\", NULL},\n{\"setminus\", \"∖\", NULL},\n{\"setmn\", \"∖\", NULL},\n{\"sext\", \"✶\", NULL},\n{\"sfr\", \"𝔰\", NULL},\n{\"sfrown\", \"⌢\", NULL},\n{\"sharp\", \"♯\", NULL},\n{\"shchcy\", \"щ\", NULL},\n{\"shcy\", \"ш\", NULL},\n{\"shortmid\", \"∣\", NULL},\n{\"shortparallel\", \"∥\", NULL},\n{\"shy\", \"­\", \"­\"},\n{\"sigma\", \"σ\", \"σ\"},\n{\"sigmaf\", \"ς\", \"ς\"},\n{\"sigmav\", \"ς\", NULL},\n{\"sim\", \"∼\", \"∼\"},\n{\"simdot\", \"⩪\", NULL},\n{\"sime\", \"≃\", NULL},\n{\"simeq\", \"≃\", NULL},\n{\"simg\", \"⪞\", NULL},\n{\"simgE\", \"⪠\", NULL},\n{\"siml\", \"⪝\", NULL},\n{\"simlE\", \"⪟\", NULL},\n{\"simne\", \"≆\", NULL},\n{\"simplus\", \"⨤\", NULL},\n{\"simrarr\", \"⥲\", NULL},\n{\"slarr\", \"←\", NULL},\n{\"smallsetminus\", \"∖\", NULL},\n{\"smashp\", \"⨳\", NULL},\n{\"smeparsl\", \"⧤\", NULL},\n{\"smid\", \"∣\", NULL},\n{\"smile\", \"⌣\", NULL},\n{\"smt\", \"⪪\", NULL},\n{\"smte\", \"⪬\", NULL},\n{\"smtes\", \"⪬︀\", NULL},\n{\"softcy\", \"ь\", NULL},\n{\"sol\", \"/\", NULL},\n{\"solb\", \"⧄\", NULL},\n{\"solbar\", \"⌿\", NULL},\n{\"sopf\", \"𝕤\", NULL},\n{\"spades\", \"♠\", \"♠\"},\n{\"spadesuit\", \"♠\", NULL},\n{\"spar\", \"∥\", NULL},\n{\"sqcap\", \"⊓\", NULL},\n{\"sqcaps\", \"⊓︀\", NULL},\n{\"sqcup\", \"⊔\", NULL},\n{\"sqcups\", \"⊔︀\", NULL},\n{\"sqsub\", \"⊏\", NULL},\n{\"sqsube\", \"⊑\", NULL},\n{\"sqsubset\", \"⊏\", NULL},\n{\"sqsubseteq\", \"⊑\", NULL},\n{\"sqsup\", \"⊐\", NULL},\n{\"sqsupe\", \"⊒\", NULL},\n{\"sqsupset\", \"⊐\", NULL},\n{\"sqsupseteq\", \"⊒\", NULL},\n{\"squ\", \"□\", NULL},\n{\"square\", \"□\", NULL},\n{\"squarf\", \"▪\", NULL},\n{\"squf\", \"▪\", NULL},\n{\"srarr\", \"→\", NULL},\n{\"sscr\", \"𝓈\", NULL},\n{\"ssetmn\", \"∖\", NULL},\n{\"ssmile\", \"⌣\", NULL},\n{\"sstarf\", \"⋆\", NULL},\n{\"star\", \"☆\", NULL},\n{\"starf\", \"★\", NULL},\n{\"straightepsilon\", \"ϵ\", NULL},\n{\"straightphi\", \"ϕ\", NULL},\n{\"strns\", \"¯\", NULL},\n{\"sub\", \"⊂\", \"⊂\"},\n{\"subE\", \"⫅\", NULL},\n{\"subdot\", \"⪽\", NULL},\n{\"sube\", \"⊆\", \"⊆\"},\n{\"subedot\", \"⫃\", NULL},\n{\"submult\", \"⫁\", NULL},\n{\"subnE\", \"⫋\", NULL},\n{\"subne\", \"⊊\", NULL},\n{\"subplus\", \"⪿\", NULL},\n{\"subrarr\", \"⥹\", NULL},\n{\"subset\", \"⊂\", NULL},\n{\"subseteq\", \"⊆\", NULL},\n{\"subseteqq\", \"⫅\", NULL},\n{\"subsetneq\", \"⊊\", NULL},\n{\"subsetneqq\", \"⫋\", NULL},\n{\"subsim\", \"⫇\", NULL},\n{\"subsub\", \"⫕\", NULL},\n{\"subsup\", \"⫓\", NULL},\n{\"succ\", \"≻\", NULL},\n{\"succapprox\", \"⪸\", NULL},\n{\"succcurlyeq\", \"≽\", NULL},\n{\"succeq\", \"⪰\", NULL},\n{\"succnapprox\", \"⪺\", NULL},\n{\"succneqq\", \"⪶\", NULL},\n{\"succnsim\", \"⋩\", NULL},\n{\"succsim\", \"≿\", NULL},\n{\"sum\", \"∑\", \"∑\"},\n{\"sung\", \"♪\", NULL},\n{\"sup\", \"⊃\", \"⊃\"},\n{\"sup1\", \"¹\", \"¹\"},\n{\"sup2\", \"²\", \"²\"},\n{\"sup3\", \"³\", \"³\"},\n{\"supE\", \"⫆\", NULL},\n{\"supdot\", \"⪾\", NULL},\n{\"supdsub\", \"⫘\", NULL},\n{\"supe\", \"⊇\", \"⊇\"},\n{\"supedot\", \"⫄\", NULL},\n{\"suphsol\", \"⟉\", NULL},\n{\"suphsub\", \"⫗\", NULL},\n{\"suplarr\", \"⥻\", NULL},\n{\"supmult\", \"⫂\", NULL},\n{\"supnE\", \"⫌\", NULL},\n{\"supne\", \"⊋\", NULL},\n{\"supplus\", \"⫀\", NULL},\n{\"supset\", \"⊃\", NULL},\n{\"supseteq\", \"⊇\", NULL},\n{\"supseteqq\", \"⫆\", NULL},\n{\"supsetneq\", \"⊋\", NULL},\n{\"supsetneqq\", \"⫌\", NULL},\n{\"supsim\", \"⫈\", NULL},\n{\"supsub\", \"⫔\", NULL},\n{\"supsup\", \"⫖\", NULL},\n{\"swArr\", \"⇙\", NULL},\n{\"swarhk\", \"⤦\", NULL},\n{\"swarr\", \"↙\", NULL},\n{\"swarrow\", \"↙\", NULL},\n{\"swnwar\", \"⤪\", NULL},\n{\"szlig\", \"ß\", \"ß\"},\n{\"target\", \"⌖\", NULL},\n{\"tau\", \"τ\", \"τ\"},\n{\"tbrk\", \"⎴\", NULL},\n{\"tcaron\", \"ť\", NULL},\n{\"tcedil\", \"ţ\", NULL},\n{\"tcy\", \"т\", NULL},\n{\"tdot\", \"⃛\", NULL},\n{\"telrec\", \"⌕\", NULL},\n{\"tfr\", \"𝔱\", NULL},\n{\"there4\", \"∴\", \"∴\"},\n{\"therefore\", \"∴\", NULL},\n{\"theta\", \"θ\", \"θ\"},\n{\"thetasym\", \"ϑ\", \"ϑ\"},\n{\"thetav\", \"ϑ\", NULL},\n{\"thickapprox\", \"≈\", NULL},\n{\"thicksim\", \"∼\", NULL},\n{\"thinsp\", \" \", \" \"},\n{\"thkap\", \"≈\", NULL},\n{\"thksim\", \"∼\", NULL},\n{\"thorn\", \"þ\", \"þ\"},\n{\"tilde\", \"˜\", \"˜\"},\n{\"times\", \"×\", \"×\"},\n{\"timesb\", \"⊠\", NULL},\n{\"timesbar\", \"⨱\", NULL},\n{\"timesd\", \"⨰\", NULL},\n{\"tint\", \"∭\", NULL},\n{\"toea\", \"⤨\", NULL},\n{\"top\", \"⊤\", NULL},\n{\"topbot\", \"⌶\", NULL},\n{\"topcir\", \"⫱\", NULL},\n{\"topf\", \"𝕥\", NULL},\n{\"topfork\", \"⫚\", NULL},\n{\"tosa\", \"⤩\", NULL},\n{\"tprime\", \"‴\", NULL},\n{\"trade\", \"™\", \"™\"},\n{\"triangle\", \"▵\", NULL},\n{\"triangledown\", \"▿\", NULL},\n{\"triangleleft\", \"◃\", NULL},\n{\"trianglelefteq\", \"⊴\", NULL},\n{\"triangleq\", \"≜\", NULL},\n{\"triangleright\", \"▹\", NULL},\n{\"trianglerighteq\", \"⊵\", NULL},\n{\"tridot\", \"◬\", NULL},\n{\"trie\", \"≜\", NULL},\n{\"triminus\", \"⨺\", NULL},\n{\"triplus\", \"⨹\", NULL},\n{\"trisb\", \"⧍\", NULL},\n{\"tritime\", \"⨻\", NULL},\n{\"trpezium\", \"⏢\", NULL},\n{\"tscr\", \"𝓉\", NULL},\n{\"tscy\", \"ц\", NULL},\n{\"tshcy\", \"ћ\", NULL},\n{\"tstrok\", \"ŧ\", NULL},\n{\"twixt\", \"≬\", NULL},\n{\"twoheadleftarrow\", \"↞\", NULL},\n{\"twoheadrightarrow\", \"↠\", NULL},\n{\"uArr\", \"⇑\", \"⇑\"},\n{\"uHar\", \"⥣\", NULL},\n{\"uacute\", \"ú\", \"ú\"},\n{\"uarr\", \"↑\", \"↑\"},\n{\"ubrcy\", \"ў\", NULL},\n{\"ubreve\", \"ŭ\", NULL},\n{\"ucirc\", \"û\", \"û\"},\n{\"ucy\", \"у\", NULL},\n{\"udarr\", \"⇅\", NULL},\n{\"udblac\", \"ű\", NULL},\n{\"udhar\", \"⥮\", NULL},\n{\"ufisht\", \"⥾\", NULL},\n{\"ufr\", \"𝔲\", NULL},\n{\"ugrave\", \"ù\", \"ù\"},\n{\"uharl\", \"↿\", NULL},\n{\"uharr\", \"↾\", NULL},\n{\"uhblk\", \"▀\", NULL},\n{\"ulcorn\", \"⌜\", NULL},\n{\"ulcorner\", \"⌜\", NULL},\n{\"ulcrop\", \"⌏\", NULL},\n{\"ultri\", \"◸\", NULL},\n{\"umacr\", \"ū\", NULL},\n{\"uml\", \"¨\", \"¨\"},\n{\"uogon\", \"ų\", NULL},\n{\"uopf\", \"𝕦\", NULL},\n{\"uparrow\", \"↑\", NULL},\n{\"updownarrow\", \"↕\", NULL},\n{\"upharpoonleft\", \"↿\", NULL},\n{\"upharpoonright\", \"↾\", NULL},\n{\"uplus\", \"⊎\", NULL},\n{\"upsi\", \"υ\", NULL},\n{\"upsih\", \"ϒ\", \"ϒ\"},\n{\"upsilon\", \"υ\", \"υ\"},\n{\"upuparrows\", \"⇈\", NULL},\n{\"urcorn\", \"⌝\", NULL},\n{\"urcorner\", \"⌝\", NULL},\n{\"urcrop\", \"⌎\", NULL},\n{\"uring\", \"ů\", NULL},\n{\"urtri\", \"◹\", NULL},\n{\"uscr\", \"𝓊\", NULL},\n{\"utdot\", \"⋰\", NULL},\n{\"utilde\", \"ũ\", NULL},\n{\"utri\", \"▵\", NULL},\n{\"utrif\", \"▴\", NULL},\n{\"uuarr\", \"⇈\", NULL},\n{\"uuml\", \"ü\", \"ü\"},\n{\"uwangle\", \"⦧\", NULL},\n{\"vArr\", \"⇕\", NULL},\n{\"vBar\", \"⫨\", NULL},\n{\"vBarv\", \"⫩\", NULL},\n{\"vDash\", \"⊨\", NULL},\n{\"vangrt\", \"⦜\", NULL},\n{\"varepsilon\", \"ϵ\", NULL},\n{\"varkappa\", \"ϰ\", NULL},\n{\"varnothing\", \"∅\", NULL},\n{\"varphi\", \"ϕ\", NULL},\n{\"varpi\", \"ϖ\", NULL},\n{\"varpropto\", \"∝\", NULL},\n{\"varr\", \"↕\", NULL},\n{\"varrho\", \"ϱ\", NULL},\n{\"varsigma\", \"ς\", NULL},\n{\"varsubsetneq\", \"⊊︀\", NULL},\n{\"varsubsetneqq\", \"⫋︀\", NULL},\n{\"varsupsetneq\", \"⊋︀\", NULL},\n{\"varsupsetneqq\", \"⫌︀\", NULL},\n{\"vartheta\", \"ϑ\", NULL},\n{\"vartriangleleft\", \"⊲\", NULL},\n{\"vartriangleright\", \"⊳\", NULL},\n{\"vcy\", \"в\", NULL},\n{\"vdash\", \"⊢\", NULL},\n{\"vee\", \"∨\", NULL},\n{\"veebar\", \"⊻\", NULL},\n{\"veeeq\", \"≚\", NULL},\n{\"vellip\", \"⋮\", NULL},\n{\"verbar\", \"|\", NULL},\n{\"vert\", \"|\", NULL},\n{\"vfr\", \"𝔳\", NULL},\n{\"vltri\", \"⊲\", NULL},\n{\"vnsub\", \"⊂⃒\", NULL},\n{\"vnsup\", \"⊃⃒\", NULL},\n{\"vopf\", \"𝕧\", NULL},\n{\"vprop\", \"∝\", NULL},\n{\"vrtri\", \"⊳\", NULL},\n{\"vscr\", \"𝓋\", NULL},\n{\"vsubnE\", \"⫋︀\", NULL},\n{\"vsubne\", \"⊊︀\", NULL},\n{\"vsupnE\", \"⫌︀\", NULL},\n{\"vsupne\", \"⊋︀\", NULL},\n{\"vzigzag\", \"⦚\", NULL},\n{\"wcirc\", \"ŵ\", NULL},\n{\"wedbar\", \"⩟\", NULL},\n{\"wedge\", \"∧\", NULL},\n{\"wedgeq\", \"≙\", NULL},\n{\"weierp\", \"℘\", \"℘\"},\n{\"wfr\", \"𝔴\", NULL},\n{\"wopf\", \"𝕨\", NULL},\n{\"wp\", \"℘\", NULL},\n{\"wr\", \"≀\", NULL},\n{\"wreath\", \"≀\", NULL},\n{\"wscr\", \"𝓌\", NULL},\n{\"xcap\", \"⋂\", NULL},\n{\"xcirc\", \"◯\", NULL},\n{\"xcup\", \"⋃\", NULL},\n{\"xdtri\", \"▽\", NULL},\n{\"xfr\", \"𝔵\", NULL},\n{\"xhArr\", \"⟺\", NULL},\n{\"xharr\", \"⟷\", NULL},\n{\"xi\", \"ξ\", \"ξ\"},\n{\"xlArr\", \"⟸\", NULL},\n{\"xlarr\", \"⟵\", NULL},\n{\"xmap\", \"⟼\", NULL},\n{\"xnis\", \"⋻\", NULL},\n{\"xodot\", \"⨀\", NULL},\n{\"xopf\", \"𝕩\", NULL},\n{\"xoplus\", \"⨁\", NULL},\n{\"xotime\", \"⨂\", NULL},\n{\"xrArr\", \"⟹\", NULL},\n{\"xrarr\", \"⟶\", NULL},\n{\"xscr\", \"𝓍\", NULL},\n{\"xsqcup\", \"⨆\", NULL},\n{\"xuplus\", \"⨄\", NULL},\n{\"xutri\", \"△\", NULL},\n{\"xvee\", \"⋁\", NULL},\n{\"xwedge\", \"⋀\", NULL},\n{\"yacute\", \"ý\", \"ý\"},\n{\"yacy\", \"я\", NULL},\n{\"ycirc\", \"ŷ\", NULL},\n{\"ycy\", \"ы\", NULL},\n{\"yen\", \"¥\", \"¥\"},\n{\"yfr\", \"𝔶\", NULL},\n{\"yicy\", \"ї\", NULL},\n{\"yopf\", \"𝕪\", NULL},\n{\"yscr\", \"𝓎\", NULL},\n{\"yucy\", \"ю\", NULL},\n{\"yuml\", \"ÿ\", \"ÿ\"},\n{\"zacute\", \"ź\", NULL},\n{\"zcaron\", \"ž\", NULL},\n{\"zcy\", \"з\", NULL},\n{\"zdot\", \"ż\", NULL},\n{\"zeetrf\", \"ℨ\", NULL},\n{\"zeta\", \"ζ\", \"ζ\"},\n{\"zfr\", \"𝔷\", NULL},\n{\"zhcy\", \"ж\", NULL},\n{\"zigrarr\", \"⇝\", NULL},\n{\"zopf\", \"𝕫\", NULL},\n{\"zscr\", \"𝓏\", NULL},\n{\"zwj\", \"‍\", \"‍\"},\n{\"zwnj\", \"‌\", \"‌\"},\n};\n#endif /* HTML_CHARREFS_H */\n"
  },
  {
    "path": "src/html_common.hh",
    "content": "#ifndef __HTML_COMMON_HH__\n#define __HTML_COMMON_HH__\n\n#include \"url.h\"\n#include \"bw.h\"\n\n#include \"lout/misc.hh\"\n#include \"dw/core.hh\"\n#include \"dw/image.hh\"\n#include \"dw/style.hh\"\n\n#include \"image.hh\"\n\n#include \"form.hh\"\n\n#include \"styleengine.hh\"\n\n/*\n * Macros\n */\n\n// \"html struct\" to Textblock\n#define HT2TB(html)  ((Textblock*)(html->dw))\n// \"html struct\" to \"Layout\"\n#define HT2LT(html)  ((Layout*)html->bw->render_layout)\n// \"Image\" to \"Dw Widget\"\n#define IM2DW(Image)  ((Widget*)Image->dw)\n// Top of the parsing stack\n#define S_TOP(html)  (html->stack->getRef(html->stack->size()-1))\n\n// Add a bug-meter message.\n#define BUG_MSG(...)                               \\\n   D_STMT_START {                                  \\\n         html->bugMessage(__VA_ARGS__);            \\\n   } D_STMT_END\n\n\n/*\n * Typedefs\n */\n\ntypedef enum {\n   DT_NONE,\n   DT_UNRECOGNIZED,\n   DT_HTML,\n   DT_XHTML\n} DilloHtmlDocumentType;\n\ntypedef enum {\n   DILLO_HTML_PARSE_MODE_INIT = 0,\n   DILLO_HTML_PARSE_MODE_STASH,\n   DILLO_HTML_PARSE_MODE_STASH_AND_BODY,\n   DILLO_HTML_PARSE_MODE_VERBATIM,\n   DILLO_HTML_PARSE_MODE_BODY,\n   DILLO_HTML_PARSE_MODE_PRE\n} DilloHtmlParseMode;\n\ntypedef enum {\n   DILLO_HTML_TABLE_MODE_NONE,  /* no table at all */\n   DILLO_HTML_TABLE_MODE_TOP,   /* outside of <tr> */\n   DILLO_HTML_TABLE_MODE_TR,    /* inside of <tr>, outside of <td> */\n   DILLO_HTML_TABLE_MODE_TD     /* inside of <td> */\n} DilloHtmlTableMode;\n\ntypedef enum {\n   DILLO_HTML_TABLE_BORDER_SEPARATE,\n   DILLO_HTML_TABLE_BORDER_COLLAPSE\n} DilloHtmlTableBorderMode;\n\ntypedef enum {\n   HTML_LIST_NONE,\n   HTML_LIST_UNORDERED,\n   HTML_LIST_ORDERED\n} DilloHtmlListMode;\n\ntypedef enum {\n   IN_NONE        = 0,\n   IN_HTML        = 1 << 0,\n   IN_HEAD        = 1 << 1,\n   IN_BODY        = 1 << 2,\n   IN_FORM        = 1 << 3,\n   IN_SELECT      = 1 << 4,\n   IN_OPTION      = 1 << 5,\n   IN_OPTGROUP    = 1 << 6,\n   IN_TEXTAREA    = 1 << 7,\n   IN_BUTTON      = 1 << 8,\n   IN_MAP         = 1 << 9,\n   IN_PRE         = 1 << 10,\n   IN_LI          = 1 << 11,\n   IN_MEDIA       = 1 << 12,\n   IN_META_HACK   = 1 << 13,\n   IN_A           = 1 << 14,\n   IN_EOF         = 1 << 15,\n} DilloHtmlProcessingState;\n\n/*\n * Data Structures\n */\n\ntypedef struct {\n   DilloUrl *url;\n   DilloImage *image;\n} DilloHtmlImage;\n\ntypedef struct {\n   DilloHtmlParseMode parse_mode;\n   DilloHtmlTableMode table_mode;\n   DilloHtmlTableBorderMode table_border_mode;\n   bool cell_text_align_set;\n   bool display_none;\n   DilloHtmlListMode list_type;\n   int list_number;\n\n   /* TagInfo index for the tag that's being processed */\n   int tag_idx;\n\n   dw::core::Widget *textblock, *table;\n\n   /* This is used to align list items (especially in enumerated lists) */\n   dw::core::Widget *ref_list_item;\n\n   /* This is used for list items etc; if it is set to TRUE, breaks\n      have to be \"handed over\" (see Html_add_indented and\n      Html_eventually_pop_dw). */\n   bool hand_over_break;\n} DilloHtmlState;\n\n/*\n * Classes\n */\n\nclass DilloHtml {\nprivate:\n   class HtmlLinkReceiver: public dw::core::Layout::LinkReceiver {\n   public:\n      DilloHtml *html;\n\n      bool enter (dw::core::Widget *widget, int link, int img, int x, int y);\n      bool press (dw::core::Widget *widget, int link, int img, int x, int y,\n                  dw::core::EventButton *event);\n      bool click (dw::core::Widget *widget, int link, int img, int x, int y,\n                  dw::core::EventButton *event);\n   };\n   HtmlLinkReceiver linkReceiver;\n\npublic:  //BUG: for now everything is public\n\n   BrowserWindow *bw;\n   DilloUrl *page_url, *base_url;\n   dw::core::Widget *dw;    /* this is duplicated in the stack */\n\n   /* -------------------------------------------------------------------*/\n   /* Variables required at parsing time                                 */\n   /* -------------------------------------------------------------------*/\n   char *Start_Buf;\n   int Start_Ofs;\n   char *content_type, *charset;\n   bool stop_parser;\n\n   size_t CurrOfs, OldOfs, OldLine;\n\n   DilloHtmlDocumentType DocType; /* as given by DOCTYPE tag */\n   float DocTypeVersion;          /* HTML or XHTML version number */\n\n   /* vector of remote CSS resources, as given by the LINK element */\n   lout::misc::SimpleVector<DilloUrl*> *cssUrls;\n\n   lout::misc::SimpleVector<DilloHtmlState> *stack;\n   StyleEngine *styleEngine;\n\n   int InFlags; /* tracks which elements we are in */\n\n   Dstr *Stash;\n   bool StashSpace;\n\n   int pre_column;        /* current column, used in PRE tags with tabs */\n   bool PreFirstChar;     /* used to skip the first CR or CRLF in PRE tags */\n   bool PrevWasCR;        /* Flag to help parsing of \"\\r\\n\" in PRE tags */\n   bool PrevWasOpenTag;   /* Flag to help deferred parsing of white space */\n   bool InVisitedLink;    /* used to 'contrast_visited_colors' */\n   bool ReqTagClose;      /* Flag to close the stack's top tag */\n   bool TagSoup;          /* Flag to enable the parser's cleanup functions */\n   bool loadCssFromStash; /* current stash content should be loaded as CSS */\n   bool PrevWasBodyClose; /* set when </body> is found */\n   bool PrevWasHtmlClose; /* set when </html> is found */\n\n   /* element counters: used for validation purposes.\n    * ATM they're used as three state flags {0,1,>1} */\n   uchar_t Num_HTML, Num_HEAD, Num_BODY, Num_TITLE;\n\n   Dstr *attr_data;       /* Buffer for attribute value */\n\n   int32_t non_css_link_color; /* as provided by link attribute in BODY */\n   int32_t non_css_visited_color; /* as provided by vlink attribute in BODY */\n   int32_t visited_color; /* as computed according to CSS */\n\n   /* -------------------------------------------------------------------*/\n   /* Variables required after parsing (for page functionality)          */\n   /* -------------------------------------------------------------------*/\n   lout::misc::SimpleVector<DilloHtmlForm*> *forms;\n   lout::misc::SimpleVector<DilloHtmlInput*> *inputs_outside_form;\n   lout::misc::SimpleVector<DilloUrl*> *links;\n   lout::misc::SimpleVector<DilloHtmlImage*> *images;\n   dw::ImageMapsList maps;\n\n   /* -------------------------------------------------------------------*/\n   /* Variables used by Gemini, Gopher and Markdown parsers                      */\n   /* -------------------------------------------------------------------*/\n   bool in_pre;    /* currently in a preformatted section */\n   bool in_link;   /* currently in a link content */\n   int list_level; /* nesting level of current list */\n   char last_partial_line[1024]; /* last partial line, when broken in the middle */\n\nprivate:\n   void freeParseData();\n   void initDw();  /* Used by the constructor */\n\npublic:\n   DilloHtml(BrowserWindow *bw, const DilloUrl *url, const char *content_type);\n   ~DilloHtml();\n   void bugMessage(const char *format, ... );\n   void connectSignals(dw::core::Widget *dw);\n   void write(char *Buf, int BufSize, int Eof);\n   int getCurrLineNumber();\n   void finishParsing(int ClientKey);\n   int formNew(DilloHtmlMethod method, const DilloUrl *action,\n               DilloHtmlEnc enc, const char *charset);\n   DilloHtmlForm *getCurrentForm ();\n   bool_t unloadedImages();\n   void loadImages (const DilloUrl *pattern);\n   void addCssUrl(const DilloUrl *url);\n\n   // useful shortcuts\n   inline void startElement (int tag)\n   { styleEngine->startElement (tag, bw); }\n   inline void startElement (const char *tagname)\n   { styleEngine->startElement (tagname, bw); }\n\n   inline dw::core::style::Style *backgroundStyle ()\n   { return styleEngine->backgroundStyle (bw); }\n   inline dw::core::style::Style *style ()\n   { return styleEngine->style (bw); }\n   inline dw::core::style::Style *wordStyle ()\n   { return styleEngine->wordStyle (bw); }\n\n   inline void restyle () { styleEngine->restyle (bw); }\n\n};\n\n/*\n * Parser functions\n */\n\nint a_Html_tag_index(const char *tag);\n\nconst char *a_Html_get_attr(DilloHtml *html,\n                            const char *tag,\n                            int tagsize,\n                            const char *attrname);\n\nchar *a_Html_get_attr_wdef(DilloHtml *html,\n                           const char *tag,\n                           int tagsize,\n                           const char *attrname,\n                           const char *def);\n\nDilloUrl *a_Html_url_new(DilloHtml *html,\n                         const char *url_str, const char *base_url,\n                         int use_base_url);\n\nvoid a_Html_common_image_attrs(DilloHtml *html, const char *tag, int tagsize);\nDilloImage *a_Html_image_new(DilloHtml *html, const char *tag, int tagsize);\n\nchar *a_Html_parse_entities(DilloHtml *html, const char *token, int toksize);\nvoid a_Html_pop_tag(DilloHtml *html, int TagIdx);\nvoid a_Html_stash_init(DilloHtml *html);\nint32_t a_Html_color_parse(DilloHtml *html, const char *str,\n                           int32_t default_color);\ndw::core::style::Length a_Html_parse_length (DilloHtml *html,\n                                             const char *attr);\nvoid a_Html_tag_set_align_attr(DilloHtml *html, const char *tag, int tagsize);\nbool a_Html_tag_set_valign_attr(DilloHtml *html,\n                                const char *tag, int tagsize);\n\nvoid a_Html_load_stylesheet(DilloHtml *html, DilloUrl *url);\n\n#endif /* __HTML_COMMON_HH__ */\n"
  },
  {
    "path": "src/image.cc",
    "content": "/*\n * File: image.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>,\n *                         Sebastian Geerken  <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * This file implements image data transfer methods. It handles the transfer\n * of data from an Image to a DwImage widget.\n */\n\n#include \"msg.h\"\n\n#include \"image.hh\"\n#include \"dw/core.hh\"\n#include \"dw/image.hh\"\n\nusing namespace dw::core;\n\n// Image to Object-ImgRenderer macro\n#define I2IR(Image)  ((dw::core::ImgRenderer*)(Image->img_rndr))\n\n\n/*\n * Create and initialize a new image structure.\n */\nDilloImage *a_Image_new(void *layout, void *img_rndr, int32_t bg_color)\n{\n   DilloImage *Image;\n\n   Image = dNew(DilloImage, 1);\n   Image->layout = layout;\n   Image->img_rndr = img_rndr;\n   Image->width = 0;\n   Image->height = 0;\n   Image->bg_color = bg_color;\n   Image->ScanNumber = 0;\n   Image->BitVec = NULL;\n   Image->State = IMG_Empty;\n\n   Image->RefCount = 0;\n\n   return Image;\n}\n\n/*\n * Create and initialize a new image structure with an image widget.\n */\nDilloImage *a_Image_new_with_dw(void *layout, const char *alt_text,\n                                int32_t bg_color)\n{\n   dw::Image *dw = new dw::Image(alt_text);\n   return a_Image_new(layout, (void*)(dw::core::ImgRenderer*)dw, bg_color);\n}\n\n/*\n * Return the image renderer as a widget. This is somewhat tricky,\n * since simple casting leads to wrong (and hard to debug) results,\n * because of multiple inheritance. This function can be used from C\n * code, where only access to void* is possible.\n */\nvoid *a_Image_get_dw(DilloImage *Image)\n{\n   return (dw::Image*)(dw::core::ImgRenderer*)Image->img_rndr;\n}\n/*\n * Deallocate an Image structure\n */\nstatic void Image_free(DilloImage *Image)\n{\n   a_Bitvec_free(Image->BitVec);\n   dFree(Image);\n}\n\n/*\n * Unref and free if necessary\n * Do nothing if the argument is NULL\n */\nvoid a_Image_unref(DilloImage *Image)\n{\n   _MSG(\" %d \", Image->RefCount);\n   if (Image && --Image->RefCount == 0)\n      Image_free(Image);\n}\n\n/*\n * Add a reference to an Image struct\n * Do nothing if the argument is NULL\n */\nvoid a_Image_ref(DilloImage *Image)\n{\n   if (Image)\n      ++Image->RefCount;\n}\n\n/*\n * Set initial parameters of the image\n */\nvoid a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url,\n                       int version, uint_t width, uint_t height,\n                       DilloImgType type)\n{\n   _MSG(\"a_Image_set_parms: width=%d height=%d iw=%d ih=%d\\n\",\n        width, height, Image->width, Image->height);\n\n   /* Resize from 0,0 to width,height */\n   bool resize = true;\n   I2IR(Image)->setBuffer((Imgbuf*)v_imgbuf, resize);\n\n   if (!Image->BitVec)\n      Image->BitVec = a_Bitvec_new(height);\n   Image->width = width;\n   Image->height = height;\n   Image->State = IMG_SetParms;\n}\n\n/*\n * Implement the write method\n */\nvoid a_Image_write(DilloImage *Image, uint_t y)\n{\n   _MSG(\"a_Image_write\\n\");\n   dReturn_if_fail ( y < Image->height );\n\n   /* Update the row in DwImage */\n   I2IR(Image)->drawRow(y);\n   a_Bitvec_set_bit(Image->BitVec, y);\n   Image->State = IMG_Write;\n}\n\n/*\n * Implement the close method\n */\nvoid a_Image_close(DilloImage *Image)\n{\n   _MSG(\"a_Image_close\\n\");\n   I2IR(Image)->finish();\n}\n\n/*\n * Implement the abort method\n */\nvoid a_Image_abort(DilloImage *Image)\n{\n   _MSG(\"a_Image_abort\\n\");\n   I2IR(Image)->fatal();\n}\n\n"
  },
  {
    "path": "src/image.hh",
    "content": "#ifndef __IMAGE_HH__\n#define __IMAGE_HH__\n\n// The DilloImage data-structure and methods\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\n#include \"bitvec.h\"\n#include \"url.h\"\n\n/*\n * Defines\n */\n\n/* Arbitrary maximum for image size (to avoid image size-crafting attacks). */\n#define IMAGE_MAX_AREA (6000 * 6000)\n\n/*\n * Types\n */\n\ntypedef struct _DilloImage DilloImage;\n\ntypedef enum {\n   DILLO_IMG_TYPE_INDEXED,\n   DILLO_IMG_TYPE_RGB,\n   DILLO_IMG_TYPE_GRAY,\n   DILLO_IMG_TYPE_CMYK_INV,\n   DILLO_IMG_TYPE_NOTSET    /* Initial value */\n} DilloImgType;\n\n/* These will reflect the Image's \"state\" */\ntypedef enum {\n   IMG_Empty,      /* Just created the entry */\n   IMG_SetParms,   /* Parameters set */\n   IMG_SetCmap,    /* Color map set */\n   IMG_Write,      /* Feeding the entry */\n   IMG_Close,      /* Whole image got! */\n   IMG_Abort       /* Image transfer aborted */\n} ImageState;\n\nstruct _DilloImage {\n   void *layout, *img_rndr;\n\n   /* Parameters as told by image data */\n   uint_t width;\n   uint_t height;\n\n   int32_t bg_color;        /* Background color */\n   bitvec_t *BitVec;        /* Bit vector for decoded rows */\n   uint_t ScanNumber;       /* Current decoding scan */\n   ImageState State;        /* Processing status */\n\n   int RefCount;            /* Reference counter */\n};\n\n\n/*\n * Function prototypes\n */\nDilloImage *a_Image_new(void *layout, void *img_rndr, int32_t bg_color);\nDilloImage *a_Image_new_with_dw(void *layout, const char *alt_text,\n                                int32_t bg_color);\nvoid *a_Image_get_dw(DilloImage *Image);\nvoid a_Image_ref(DilloImage *Image);\nvoid a_Image_unref(DilloImage *Image);\n\nvoid a_Image_set_parms(DilloImage *Image, void *v_imgbuf, DilloUrl *url,\n                       int version, uint_t width, uint_t height,\n                       DilloImgType type);\nvoid a_Image_write(DilloImage *Image, uint_t y);\nvoid a_Image_close(DilloImage *Image);\nvoid a_Image_abort(DilloImage *Image);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __IMAGE_HH__ */\n\n"
  },
  {
    "path": "src/imgbuf.cc",
    "content": "/*\n * File: imgbuf.cc\n *\n * Copyright (C) 2008 Jorge Arellano Cid <jcid@dillo.org>,\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include \"msg.h\"\n#include \"imgbuf.hh\"\n#include \"dw/core.hh\"\n#include \"dw/image.hh\"\n\nusing namespace dw::core;\n\n/*\n * Local data\n */\nstatic size_t linebuf_size = 0;\nstatic uchar_t *linebuf = NULL;\n\n\n/*\n * Decode 'buf' (an image line) into RGB format.\n */\nstatic uchar_t *Imgbuf_rgb_line(const uchar_t *buf,\n                                DilloImgType type, uchar_t *cmap,\n                                uint_t width, uint_t y)\n{\n   uint_t x;\n\n   switch (type) {\n   case DILLO_IMG_TYPE_INDEXED:\n      if (cmap) {\n         for (x = 0; x < width; x++)\n            memcpy(linebuf + x * 3, cmap + buf[x] * 3, 3);\n      } else {\n         MSG_WARN(\"Gif:: image lacks a color map\\n\");\n      }\n      break;\n   case DILLO_IMG_TYPE_GRAY:\n      for (x = 0; x < width; x++)\n         memset(linebuf + x * 3, buf[x], 3);\n      break;\n   case DILLO_IMG_TYPE_CMYK_INV:\n      /*\n       * We treat CMYK as if it were \"RGBW\", and it works. Everyone who is\n       * trying to handle CMYK jpegs is confused by this, and supposedly\n       * the issue is that Adobe CMYK is \"wrong\" but ubiquitous.\n       */\n      for (x = 0; x < width; x++) {\n         uint_t white = buf[x * 4 + 3];\n         linebuf[x * 3] = buf[x * 4] * white / 0x100;\n         linebuf[x * 3 + 1] = buf[x * 4 + 1] * white / 0x100;\n         linebuf[x * 3 + 2] = buf[x * 4 + 2] * white / 0x100;\n      }\n      break;\n   case DILLO_IMG_TYPE_RGB:\n      /* avoid a memcpy here!  --Jcid */\n      return (uchar_t *)buf;\n   case DILLO_IMG_TYPE_NOTSET:\n      MSG_ERR(\"Imgbuf_rgb_line: type not set...\\n\");\n      break;\n   }\n   return linebuf;\n}\n\n// Wrappers for Imgbuf -------------------------------------------------------\n\n/*\n * Increment reference count for an Imgbuf\n */\nvoid a_Imgbuf_ref(void *v_imgbuf)\n{\n   ((Imgbuf*)v_imgbuf)->ref();\n}\n\n/*\n * Decrement reference count for an Imgbuf\n */\nvoid a_Imgbuf_unref(void *v_imgbuf)\n{\n   if (v_imgbuf)\n      ((Imgbuf*)v_imgbuf)->unref();\n}\n\n/*\n * Create a new Imgbuf\n */\nvoid *a_Imgbuf_new(void *layout, int img_type, uint_t width, uint_t height,\n                   double gamma)\n{\n   if (!layout) {\n      MSG_ERR(\"a_Imgbuf_new: layout is NULL.\\n\");\n      exit(1);\n   }\n   // Assert linebuf is wide enough.\n   if (3 * width > linebuf_size) {\n      linebuf_size = 3 * width;\n      linebuf = (uchar_t*) dRealloc(linebuf, linebuf_size);\n   }\n\n   return (void*)((Layout*)layout)->createImgbuf(Imgbuf::RGB, width, height,\n                                                 gamma);\n}\n\n/*\n * Last reference for this Imgbuf?\n */\nint a_Imgbuf_last_reference(void *v_imgbuf)\n{\n   return ((Imgbuf*)v_imgbuf)->lastReference () ? 1 : 0;\n}\n\n/*\n * Update the root buffer of an imgbuf.\n */\nvoid a_Imgbuf_update(void *v_imgbuf, const uchar_t *buf, DilloImgType type,\n                     uchar_t *cmap, uint_t width, uint_t height, uint_t y)\n\n{\n   dReturn_if_fail ( y < height );\n\n   /* Decode 'buf' and copy it into the imgbuf */\n   uchar_t *newbuf = Imgbuf_rgb_line(buf, type, cmap, width, y);\n   ((Imgbuf*)v_imgbuf)->copyRow(y, (byte *)newbuf);\n}\n\n/*\n * Reset for a new scan from a multiple-scan image.\n */\nvoid a_Imgbuf_new_scan(void *v_imgbuf)\n{\n   ((Imgbuf*)v_imgbuf)->newScan();\n}\n\n"
  },
  {
    "path": "src/imgbuf.hh",
    "content": "#ifndef __IMGBUF_HH__\n#define __IMGBUF_HH__\n\n// Imgbuf wrappers\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\n#include \"image.hh\"\n\n/*\n * Function prototypes\n */\nvoid a_Imgbuf_ref(void *v_imgbuf);\nvoid a_Imgbuf_unref(void *v_imgbuf);\nvoid *a_Imgbuf_new(void *v_ir, int img_type, uint_t width, uint_t height,\n                   double gamma);\nint a_Imgbuf_last_reference(void *v_imgbuf);\nvoid a_Imgbuf_update(void *v_imgbuf, const uchar_t *buf, DilloImgType type,\n                     uchar_t *cmap, uint_t width, uint_t height, uint_t y);\nvoid a_Imgbuf_new_scan(void *v_imgbuf);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __IMGBUF_HH__ */\n\n"
  },
  {
    "path": "src/jpeg.c",
    "content": "/*\n * File: jpeg.c\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * The jpeg decoder for dillo. It is responsible for decoding JPEG data\n * and transferring it to the dicache. It uses libjpeg to do the actual\n * decoding.\n */\n\n#include <config.h>\n#ifdef ENABLE_JPEG\n\n#include <stdio.h>\n#include <setjmp.h>\n\n/* avoid a redefinition of HAVE_STDLIB_H with old jpeglib.h */\n#ifdef HAVE_STDLIB_H\n#  undef HAVE_STDLIB_H\n#endif\n#include <jpeglib.h>\n#ifdef HAVE_STDLIB_H\n#  undef HAVE_STDLIB_H\n#endif\n\n#include \"image.hh\"\n#include \"cache.h\"\n#include \"dicache.h\"\n#include \"capi.h\"       /* get cache entry status */\n#include \"msg.h\"\n\ntypedef enum {\n   DILLO_JPEG_INIT,\n   DILLO_JPEG_STARTING,\n   DILLO_JPEG_READ_BEGIN_SCAN,\n   DILLO_JPEG_READ_IN_SCAN,\n   DILLO_JPEG_READ_END_SCAN,\n   DILLO_JPEG_DONE,\n   DILLO_JPEG_ERROR\n} DilloJpegState;\n\ntypedef struct DilloJpeg DilloJpeg;\n\n/* An implementation of a suspending source manager */\n\ntypedef struct {\n   struct jpeg_source_mgr pub;  /* public fields */\n   DilloJpeg *jpeg;             /* a pointer back to the jpeg object */\n} my_source_mgr;\n\nstruct my_error_mgr {\n   struct jpeg_error_mgr pub;    /* \"public\" fields */\n   jmp_buf setjmp_buffer;        /* for return to caller */\n};\ntypedef struct my_error_mgr *my_error_ptr;\n\nstruct DilloJpeg {\n   DilloImage *Image;\n   DilloUrl *url;\n   int version;\n\n   my_source_mgr Src;\n\n   DilloJpegState state;\n   size_t Start_Ofs, Skip, NewStart;\n   char *Data;\n\n   uint_t y;\n\n   struct jpeg_decompress_struct cinfo;\n   struct my_error_mgr jerr;\n};\n\n/*\n * Forward declarations\n */\nstatic void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize);\n\n\n/* this is the routine called by libjpeg when it detects an error. */\nMETHODDEF(void) Jpeg_errorexit (j_common_ptr cinfo)\n{\n   /* display message and return to setjmp buffer */\n   my_error_ptr myerr = (my_error_ptr) cinfo->err;\n   if (prefs.show_msg) {\n      DilloJpeg *jpeg =\n                     ((my_source_mgr *) ((j_decompress_ptr) cinfo)->src)->jpeg;\n      MSG_WARN(\"\\\"%s\\\": \", URL_STR(jpeg->url));\n      (*cinfo->err->output_message) (cinfo);\n   }\n   longjmp(myerr->setjmp_buffer, 1);\n}\n\n/*\n * Free the jpeg-decoding data structure.\n */\nstatic void Jpeg_free(DilloJpeg *jpeg)\n{\n   _MSG(\"Jpeg_free: jpeg=%p\\n\", jpeg);\n   jpeg_destroy_decompress(&(jpeg->cinfo));\n   dFree(jpeg);\n}\n\n/*\n * Finish the decoding process\n */\nstatic void Jpeg_close(DilloJpeg *jpeg, CacheClient_t *Client)\n{\n   _MSG(\"Jpeg_close\\n\");\n   a_Dicache_close(jpeg->url, jpeg->version, Client);\n   Jpeg_free(jpeg);\n}\n\n/*\n * The proper signature is:\n *    static void init_source(j_decompress_ptr cinfo)\n * (declaring it with no parameter avoids a compiler warning)\n */\nstatic void init_source()\n{\n}\n\nstatic boolean fill_input_buffer(j_decompress_ptr cinfo)\n{\n   DilloJpeg *jpeg = ((my_source_mgr *) cinfo->src)->jpeg;\n\n   _MSG(\"fill_input_buffer\\n\");\n#if 0\n   if (!cinfo->src->bytes_in_buffer) {\n      _MSG(\"fill_input_buffer: %ld bytes in buffer\\n\",\n           (long)cinfo->src->bytes_in_buffer);\n\n      jpeg->Start_Ofs = (ulong_t) jpeg->cinfo.src->next_input_byte -\n         (ulong_t) jpeg->Data;\n#endif\n      if (jpeg->Skip) {\n         jpeg->Start_Ofs = jpeg->NewStart + jpeg->Skip - 1;\n         jpeg->Skip = 0;\n      } else {\n         jpeg->Start_Ofs = (ulong_t) jpeg->cinfo.src->next_input_byte -\n            (ulong_t) jpeg->Data;\n      }\n      return FALSE;\n#if 0\n   }\n   return TRUE;\n#endif\n}\n\nstatic void skip_input_data(j_decompress_ptr cinfo, long num_bytes)\n{\n   DilloJpeg *jpeg;\n\n   if (num_bytes < 1)\n      return;\n   jpeg = ((my_source_mgr *) cinfo->src)->jpeg;\n\n   _MSG(\"skip_input_data: Start_Ofs = %lu, num_bytes = %ld,\"\n        \" %ld bytes in buffer\\n\",\n        (ulong_t)jpeg->Start_Ofs, num_bytes,(long)cinfo->src->bytes_in_buffer);\n\n   cinfo->src->next_input_byte += num_bytes;\n   if (num_bytes < (long)cinfo->src->bytes_in_buffer) {\n      cinfo->src->bytes_in_buffer -= num_bytes;\n   } else {\n      jpeg->Skip += num_bytes - cinfo->src->bytes_in_buffer + 1;\n      cinfo->src->bytes_in_buffer = 0;\n   }\n}\n\n/*\n * The proper signature is:\n *    static void term_source(j_decompress_ptr cinfo)\n * (declaring it with no parameter avoids a compiler warning)\n */\nstatic void term_source()\n{\n}\n\nvoid *a_Jpeg_new(DilloImage *Image, DilloUrl *url, int version)\n{\n   my_source_mgr *src;\n   DilloJpeg *jpeg = dMalloc(sizeof(*jpeg));\n   _MSG(\"a_Jpeg_new: jpeg=%p\\n\", jpeg);\n\n   jpeg->Image = Image;\n   jpeg->url = url;\n   jpeg->version = version;\n\n   jpeg->state = DILLO_JPEG_INIT;\n   jpeg->Start_Ofs = 0;\n   jpeg->Skip = 0;\n\n   /* decompression step 1 (see libjpeg.doc) */\n   jpeg->cinfo.err = jpeg_std_error(&(jpeg->jerr.pub));\n   jpeg->jerr.pub.error_exit = Jpeg_errorexit;\n\n   jpeg_create_decompress(&(jpeg->cinfo));\n\n   /* decompression step 2 (see libjpeg.doc) */\n   jpeg->cinfo.src = &jpeg->Src.pub;\n   src = &jpeg->Src;\n   src->pub.init_source = init_source;\n   src->pub.fill_input_buffer = fill_input_buffer;\n   src->pub.skip_input_data = skip_input_data;\n   src->pub.resync_to_restart = jpeg_resync_to_restart;/* use default method */\n   src->pub.term_source = term_source;\n   src->pub.bytes_in_buffer = 0;   /* forces fill_input_buffer on first read */\n   src->pub.next_input_byte = NULL;/* until buffer loaded */\n\n   src->jpeg = jpeg;\n\n   /* decompression steps continue in write method */\n   return jpeg;\n}\n\nvoid a_Jpeg_callback(int Op, void *data)\n{\n   if (Op == CA_Send) {\n      CacheClient_t *Client = data;\n      Jpeg_write(Client->CbData, Client->Buf, Client->BufSize);\n   } else if (Op == CA_Close) {\n      CacheClient_t *Client = data;\n      Jpeg_close(Client->CbData, Client);\n   } else if (Op == CA_Abort) {\n      Jpeg_free(data);\n   }\n}\n\n/*\n * Receive and process new chunks of JPEG image data\n */\nstatic void Jpeg_write(DilloJpeg *jpeg, void *Buf, uint_t BufSize)\n{\n   DilloImgType type;\n   uchar_t *linebuf;\n   JSAMPLE *array[1];\n   int num_read;\n\n   _MSG(\"Jpeg_write: (%p) Bytes in buff: %ld Ofs: %lu\\n\", jpeg,\n        (long) BufSize, (ulong_t)jpeg->Start_Ofs);\n\n   /* See if we are supposed to skip ahead. */\n   if (BufSize <= jpeg->Start_Ofs)\n      return;\n\n   /* Concatenate with the partial input, if any. */\n   jpeg->cinfo.src->next_input_byte = (uchar_t *)Buf + jpeg->Start_Ofs;\n   jpeg->cinfo.src->bytes_in_buffer = BufSize - jpeg->Start_Ofs;\n   jpeg->NewStart = BufSize;\n   jpeg->Data = Buf;\n\n   if (setjmp(jpeg->jerr.setjmp_buffer)) {\n      /* If we get here, the JPEG code has signaled an error. */\n      jpeg->state = DILLO_JPEG_ERROR;\n   }\n\n   /* Process the bytes in the input buffer. */\n   if (jpeg->state == DILLO_JPEG_INIT) {\n\n      /* decompression step 3 (see libjpeg.doc) */\n      if (jpeg_read_header(&(jpeg->cinfo), TRUE) != JPEG_SUSPENDED) {\n         type = DILLO_IMG_TYPE_GRAY;\n         if (jpeg->cinfo.num_components == 1) {\n            type = DILLO_IMG_TYPE_GRAY;\n         } else if (jpeg->cinfo.num_components == 3) {\n            type = DILLO_IMG_TYPE_RGB;\n         } else {\n            if (jpeg->cinfo.jpeg_color_space == JCS_YCCK)\n               MSG(\"YCCK JPEG. Are the colors wrong?\\n\");\n            if (!jpeg->cinfo.saw_Adobe_marker)\n               MSG(\"No adobe marker! Is the image shown in reverse video?\\n\");\n            type = DILLO_IMG_TYPE_CMYK_INV;\n         }\n         /*\n          * If a multiple-scan image is not completely in cache,\n          * use progressive display, updating as it arrives.\n          */\n         if (jpeg_has_multiple_scans(&jpeg->cinfo) &&\n             !(a_Capi_get_flags(jpeg->url) & CAPI_Completed))\n            jpeg->cinfo.buffered_image = TRUE;\n\n         /* check max image size */\n         if (jpeg->cinfo.image_width <= 0 || jpeg->cinfo.image_height <= 0 ||\n             jpeg->cinfo.image_width >\n             IMAGE_MAX_AREA / jpeg->cinfo.image_height) {\n            MSG(\"Jpeg_write: suspicious image size request %u x %u\\n\",\n                (uint_t)jpeg->cinfo.image_width,\n                (uint_t)jpeg->cinfo.image_height);\n            jpeg->state = DILLO_JPEG_ERROR;\n            return;\n         }\n\n         /** \\todo Gamma for JPEG? */\n         a_Dicache_set_parms(jpeg->url, jpeg->version, jpeg->Image,\n                             (uint_t)jpeg->cinfo.image_width,\n                             (uint_t)jpeg->cinfo.image_height,\n                             type, 1 / 2.2);\n         jpeg->Image = NULL; /* safeguard: may be freed by its owner later */\n\n         /* decompression step 4 (see libjpeg.doc) */\n         jpeg->state = DILLO_JPEG_STARTING;\n      }\n   }\n   if (jpeg->state == DILLO_JPEG_STARTING) {\n      /* decompression step 5 (see libjpeg.doc) */\n      if (jpeg_start_decompress(&(jpeg->cinfo))) {\n         jpeg->y = 0;\n         jpeg->state = jpeg->cinfo.buffered_image ?\n                          DILLO_JPEG_READ_BEGIN_SCAN : DILLO_JPEG_READ_IN_SCAN;\n      }\n   }\n\n   /*\n    * A progressive jpeg contains multiple scans that can be used to display\n    * an increasingly sharp image as it is being received. The reading of each\n    * scan must be surrounded by jpeg_start_output()/jpeg_finish_output().\n    */\n\n   if (jpeg->state == DILLO_JPEG_READ_END_SCAN) {\n      if (jpeg_finish_output(&jpeg->cinfo)) {\n         if (jpeg_input_complete(&jpeg->cinfo)) {\n            jpeg->state = DILLO_JPEG_DONE;\n         } else {\n            jpeg->state = DILLO_JPEG_READ_BEGIN_SCAN;\n         }\n      }\n   }\n\n   if (jpeg->state == DILLO_JPEG_READ_BEGIN_SCAN) {\n      if (jpeg_start_output(&jpeg->cinfo, jpeg->cinfo.input_scan_number)) {\n         a_Dicache_new_scan(jpeg->url, jpeg->version);\n         jpeg->state = DILLO_JPEG_READ_IN_SCAN;\n      }\n   }\n\n   if (jpeg->state == DILLO_JPEG_READ_IN_SCAN) {\n      linebuf = dMalloc(jpeg->cinfo.image_width *\n                         jpeg->cinfo.num_components);\n      array[0] = linebuf;\n\n      while (1) {\n         num_read = jpeg_read_scanlines(&(jpeg->cinfo), array, 1);\n         if (num_read == 0) {\n            /* out of input */\n            break;\n         }\n         a_Dicache_write(jpeg->url, jpeg->version, linebuf, jpeg->y);\n\n         jpeg->y++;\n\n         if (jpeg->y == jpeg->cinfo.image_height) {\n            /* end of scan */\n            if (!jpeg->cinfo.buffered_image) {\n               /* single scan */\n               jpeg->state = DILLO_JPEG_DONE;\n               break;\n            } else {\n               jpeg->y = 0;\n               if (jpeg_input_complete(&jpeg->cinfo)) {\n                  if (jpeg->cinfo.input_scan_number ==\n                      jpeg->cinfo.output_scan_number) {\n                     jpeg->state = DILLO_JPEG_DONE;\n                     break;\n                  } else {\n                       /* one final loop through the scanlines */\n                       jpeg_finish_output(&jpeg->cinfo);\n                       jpeg_start_output(&jpeg->cinfo,\n                                         jpeg->cinfo.input_scan_number);\n                       continue;\n                  }\n               }\n               jpeg->state = DILLO_JPEG_READ_END_SCAN;\n               if (!jpeg_finish_output(&jpeg->cinfo)) {\n                  /* out of input */\n                  break;\n               } else {\n                  if (jpeg_input_complete(&jpeg->cinfo)) {\n                     jpeg->state = DILLO_JPEG_DONE;\n                     break;\n                  } else {\n                     jpeg->state = DILLO_JPEG_READ_BEGIN_SCAN;\n                  }\n               }\n               if (!jpeg_start_output(&jpeg->cinfo,\n                                      jpeg->cinfo.input_scan_number)) {\n                  /* out of input */\n                  break;\n               }\n               a_Dicache_new_scan(jpeg->url, jpeg->version);\n               jpeg->state = DILLO_JPEG_READ_IN_SCAN;\n            }\n         }\n      }\n      dFree(linebuf);\n   }\n}\n\n#else /* ENABLE_JPEG */\n\nvoid *a_Jpeg_new() { return 0; }\nvoid a_Jpeg_callback() { return; }\n\n#endif /* ENABLE_JPEG */\n"
  },
  {
    "path": "src/keys.cc",
    "content": "/*\n * Key parser\n *\n * Copyright (C) 2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <FL/Fl.H>\n#include <stdio.h>\n#include <stdlib.h>        /* strtol */\n#include <string.h>\n#include <ctype.h>\n\n#include \"dlib/dlib.h\"\n#include \"keys.hh\"\n#include \"utf8.hh\"\n#include \"msg.h\"\n\n/*\n *  Local data types\n */\ntypedef struct {\n   const char *name;\n   KeysCommand_t cmd;\n   int modifier, key;\n} KeyBinding_t;\n\ntypedef struct {\n   const char *name;\n   const int value;\n} Mapping_t;\n\n\n/*\n *  Local data\n */\nstatic const Mapping_t keyNames[] = {\n   { \"Backspace\",   FL_BackSpace },\n   { \"Delete\",      FL_Delete    },\n   { \"Down\",        FL_Down      },\n   { \"End\",         FL_End       },\n   { \"Esc\",         FL_Escape    },\n   { \"F1\",          FL_F + 1     },\n   { \"F2\",          FL_F + 2     },\n   { \"F3\",          FL_F + 3     },\n   { \"F4\",          FL_F + 4     },\n   { \"F5\",          FL_F + 5     },\n   { \"F6\",          FL_F + 6     },\n   { \"F7\",          FL_F + 7     },\n   { \"F8\",          FL_F + 8     },\n   { \"F9\",          FL_F + 9     },\n   { \"F10\",         FL_F + 10    },\n   { \"F11\",         FL_F + 11    },\n   { \"F12\",         FL_F + 12    },\n   { \"Home\",        FL_Home      },\n   { \"Insert\",      FL_Insert    },\n   { \"Left\",        FL_Left      },\n   { \"Menu\",        FL_Menu      },\n   { \"PageDown\",    FL_Page_Down },\n   { \"PageUp\",      FL_Page_Up   },\n   { \"Print\",       FL_Print     },\n   { \"Return\",      FL_Enter     },\n   { \"Right\",       FL_Right     },\n   { \"Space\",       ' '          },\n   { \"Tab\",         FL_Tab       },\n   { \"Up\",          FL_Up        },\n   /* multimedia keys */\n   { \"Back\",        FL_Back        },\n   { \"Favorites\",   FL_Favorites   },\n   { \"Forward\",     FL_Forward     },\n   { \"HomePage\",    FL_Home_Page   },\n   { \"Mail\",        FL_Mail        },\n   { \"MediaNext\",   FL_Media_Next  },\n   { \"MediaPlay\",   FL_Media_Play  },\n   { \"MediaPrev\",   FL_Media_Prev  },\n   { \"MediaStop\",   FL_Media_Stop  },\n   { \"Refresh\",     FL_Refresh     },\n   { \"Search\",      FL_Search      },\n   { \"Sleep\",       FL_Sleep       },\n   { \"Stop\",        FL_Stop        },\n   { \"VolumeDown\",  FL_Volume_Down },\n   { \"VolumeMute\",  FL_Volume_Mute },\n   { \"VolumeUp\",    FL_Volume_Up   },\n};\n\nstatic const Mapping_t modifierNames[] = {\n   { \"Shift\",   FL_SHIFT   },\n   { \"Ctrl\",    FL_CTRL    },\n   { \"Alt\",     FL_ALT     },\n   { \"Meta\",    FL_META    },\n   { \"Button1\", FL_BUTTON1 },\n   { \"Button2\", FL_BUTTON2 },\n   { \"Button3\", FL_BUTTON3 }\n};\n\nstatic const KeyBinding_t default_keys[] = {\n   { \"nop\"          , KEYS_NOP          , 0         , 0               },\n   { \"open\"         , KEYS_OPEN         , FL_CTRL   , 'o'             },\n   { \"new-window\"   , KEYS_NEW_WINDOW   , FL_CTRL   , 'n'             },\n   { \"new-tab\"      , KEYS_NEW_TAB      , FL_CTRL   , 't'             },\n   { \"left-tab\"     , KEYS_LEFT_TAB     , FL_CTRL |\n                                          FL_SHIFT  , FL_Tab          },\n   { \"left-tab\"     , KEYS_LEFT_TAB     , FL_CTRL   , FL_Page_Up      },\n   { \"right-tab\"    , KEYS_RIGHT_TAB    , FL_CTRL   , FL_Tab          },\n   { \"right-tab\"    , KEYS_RIGHT_TAB    , FL_CTRL   , FL_Page_Down    },\n   { \"close-tab\"    , KEYS_CLOSE_TAB    , FL_CTRL   , 'w'             },\n   { \"find\"         , KEYS_FIND         , FL_CTRL   , 'f'             },\n   { \"find\"         , KEYS_FIND         , 0   , '/'             },\n   { \"websearch\"    , KEYS_WEBSEARCH    , 0   , 's'             },\n   { \"bookmarks\"    , KEYS_BOOKMARKS    , 0   , 'b'             },\n   { \"reload\"       , KEYS_RELOAD       , FL_CTRL   , 'r'             },\n   { \"stop\"         , KEYS_STOP         , 0         , 0               },\n   { \"save\"         , KEYS_SAVE         ,FL_CTRL   , 's'              },\n   { \"hide-panels\"  , KEYS_HIDE_PANELS  , 0         , FL_Escape       },\n   { \"file-menu\"    , KEYS_FILE_MENU    , FL_ALT    , 'f'             },\n   { \"close-all\"    , KEYS_CLOSE_ALL    , FL_CTRL   , 'q'             },\n   { \"back\"         , KEYS_BACK         , 0         , FL_BackSpace    },\n   { \"back\"         , KEYS_BACK         , 0         , ','             },\n   { \"forward\"      , KEYS_FORWARD      , FL_SHIFT  , FL_BackSpace    },\n   { \"forward\"      , KEYS_FORWARD      , 0         , '.'             },\n   { \"goto\"         , KEYS_GOTO         , FL_CTRL   , 'l'             },\n   { \"home\"         , KEYS_HOME         , 0   , 'h'             },\n   { \"view-source\"  , KEYS_VIEW_SOURCE  , FL_CTRL   , 'u'             },\n   { \"screen-up\"    , KEYS_SCREEN_UP    , 0         , FL_Page_Up      },\n   { \"screen-up\"    , KEYS_SCREEN_UP    , FL_SHIFT         , ' '             },\n   { \"screen-down\"  , KEYS_SCREEN_DOWN  , 0         , FL_Page_Down    },\n   { \"screen-down\"  , KEYS_SCREEN_DOWN  , 0         , ' '             },\n   { \"screen-left\"  , KEYS_SCREEN_LEFT  , 0         , 0               },\n   { \"screen-right\" , KEYS_SCREEN_RIGHT , 0         , 0               },\n   { \"line-up\"      , KEYS_LINE_UP      , 0         , FL_Up           },\n   { \"line-down\"    , KEYS_LINE_DOWN    , 0         , FL_Down         },\n   { \"left\"         , KEYS_LEFT         , 0         , FL_Left         },\n   { \"right\"        , KEYS_RIGHT        , 0         , FL_Right        },\n   { \"top\"          , KEYS_TOP          , 0         , FL_Home         },\n   { \"bottom\"       , KEYS_BOTTOM       , 0         , FL_End          },\n};\n\nstatic Dlist *bindings;\n\n\n\n/*\n * Initialize the bindings list\n */\nvoid Keys::init()\n{\n   KeyBinding_t *node;\n\n   // Fill our key bindings list\n   bindings = dList_new(32);\n   for (uint_t i = 0; i < sizeof(default_keys) / sizeof(default_keys[0]); i++) {\n      if (default_keys[i].key) {\n         node = dNew(KeyBinding_t, 1);\n         node->name = dStrdup(default_keys[i].name);\n         node->cmd = default_keys[i].cmd;\n         node->modifier = default_keys[i].modifier;\n         node->key = default_keys[i].key;\n         dList_insert_sorted(bindings, node, nodeByKeyCmp);\n      }\n   }\n}\n\n/*\n * Free data\n */\nvoid Keys::free()\n{\n   KeyBinding_t *node;\n\n   while ((node = (KeyBinding_t*)dList_nth_data(bindings, 0))) {\n      dFree((char*)node->name);\n      dList_remove_fast(bindings, node);\n      dFree(node);\n   }\n   dList_free(bindings);\n}\n\n/*\n * Compare function by {key,modifier} pairs.\n */\nint Keys::nodeByKeyCmp(const void *node, const void *key)\n{\n   KeyBinding_t *n = (KeyBinding_t*)node, *k = (KeyBinding_t*)key;\n   _MSG(\"Keys::nodeByKeyCmp modifier=%d\\n\", k->modifier);\n   return (n->key != k->key) ? (n->key - k->key) : (n->modifier - k->modifier);\n}\n\n/*\n * Look if the just pressed key is bound to a command.\n * Return value: The command if found, KEYS_NOP otherwise.\n */\nKeysCommand_t Keys::getKeyCmd()\n{\n   KeysCommand_t ret = KEYS_NOP;\n   KeyBinding_t keyNode;\n\n   keyNode.modifier = Fl::event_state() & (FL_SHIFT | FL_CTRL |FL_ALT|FL_META);\n   if (iscntrl(Fl::event_text()[0])) {\n      keyNode.key = Fl::event_key();\n   } else {\n      const char *beyond = Fl::event_text() + Fl::event_length();\n      keyNode.key = a_Utf8_decode(Fl::event_text(), beyond, NULL);\n\n      /* BUG: The idea is to drop the modifiers if their use results in a\n       * different character (e.g., if shift-8 gives '*', drop the shift,\n       * but if ctrl-6 gives '6', keep the ctrl), but we have to compare a\n       * keysym with a Unicode codepoint, which only works for characters\n       * below U+0100 (those known to latin-1).\n       */\n      if (keyNode.key != Fl::event_key())\n         keyNode.modifier = 0;\n   }\n   _MSG(\"getKeyCmd: evkey=0x%x evtext=\\'%s\\' key=0x%x, mod=0x%x\\n\",\n        Fl::event_key(), Fl::event_text(), keyNode.key, keyNode.modifier);\n   void *data = dList_find_sorted(bindings, &keyNode, nodeByKeyCmp);\n   if (data)\n      ret = ((KeyBinding_t*)data)->cmd;\n   return ret;\n}\n\n/*\n * Remove a key binding from the table.\n */\nvoid Keys::delKeyCmd(int key, int mod)\n{\n   KeyBinding_t keyNode, *node;\n   keyNode.key = key;\n   keyNode.modifier = mod;\n\n   node = (KeyBinding_t*) dList_find_sorted(bindings, &keyNode, nodeByKeyCmp);\n   if (node) {\n      dList_remove(bindings, node);\n      dFree((char*)node->name);\n      dFree(node);\n   }\n}\n\n/*\n * Takes a key name and looks it up in the mapping table. If\n * found, its key code is returned. Otherwise -1 is given\n * back.\n */\nint Keys::getKeyCode(char *keyName)\n{\n   uint_t i;\n   for (i = 0; i < sizeof(keyNames) / sizeof(keyNames[0]); i++) {\n      if (!dStrAsciiCasecmp(keyNames[i].name, keyName)) {\n         return keyNames[i].value;\n      }\n   }\n\n   return -1;\n}\n\n/*\n * Takes a command name and searches it in the mapping table.\n * Return value: command code if found, -1 otherwise\n */\nKeysCommand_t Keys::getCmdCode(const char *commandName)\n{\n   uint_t i;\n\n   for (i = 0; i < sizeof(default_keys) / sizeof(KeyBinding_t); i++) {\n      if (!dStrAsciiCasecmp(default_keys[i].name, commandName))\n         return default_keys[i].cmd;\n   }\n   return KEYS_INVALID;\n}\n\n/*\n * Takes a modifier name and looks it up in the mapping table. If\n * found, its key code is returned. Otherwise -1 is given back.\n */\nint Keys::getModifier(char *modifierName)\n{\n   uint_t i;\n   for (i = 0; i < sizeof(modifierNames) / sizeof(modifierNames[0]); i++) {\n      if (!dStrAsciiCasecmp(modifierNames[i].name, modifierName)) {\n         return modifierNames[i].value;\n      }\n   }\n\n   return -1;\n}\n\n/*\n * Given a keys command, return a shortcut for it, or 0 if there is none\n * (e.g., for KEYS_NEW_WINDOW, return CTRL+'n').\n */\nint Keys::getShortcut(KeysCommand_t cmd)\n{\n   int len = dList_length(bindings);\n\n   for (int i = 0; i < len; i++) {\n      KeyBinding_t *node = (KeyBinding_t*)dList_nth_data(bindings, i);\n      if (cmd == node->cmd)\n         return node->modifier + node->key;\n   }\n   return 0;\n}\n\n/*\n * Parse a key-combination/command-name pair, and\n * insert it into the bindings list.\n */\nvoid Keys::parseKey(char *key, char *commandName)\n{\n   char *p, *modstr, *keystr;\n   KeysCommand_t symcode;\n   int st, keymod = 0, keycode = 0;\n\n   _MSG(\"Keys::parseKey key='%s' commandName='%s'\\n\", key, commandName);\n\n   // Get command code\n   if ((symcode = getCmdCode(commandName)) == KEYS_INVALID) {\n      MSG(\"Keys::parseKey: Invalid command name: '%s'\\n\", commandName);\n      return;\n   }\n\n   // Skip space\n   for (  ; isspace(*key); ++key) ;\n   // Get modifiers\n   while(*key == '<' && (p = strchr(key, '>'))) {\n      ++key;\n      modstr = dStrndup(key, p - key);\n      if ((st = getModifier(modstr)) == -1) {\n         MSG(\"Keys::parseKey unknown modifier: %s\\n\", modstr);\n      } else {\n         keymod |= st;\n      }\n      dFree(modstr);\n      key = p + 1;\n   }\n   // Allow trailing space after keyname\n   keystr = (*key && (p = strchr(key + 1, ' '))) ? dStrndup(key, p - key - 1) :\n                                                   dStrdup(key);\n   // Get key code\n   if (!key[1]) {\n      keycode = *key;\n   } else if (a_Utf8_char_count(keystr, strlen(keystr)) == 1) {\n      const char *beyond = keystr + strlen(keystr);\n      keycode = a_Utf8_decode(keystr, beyond, NULL);\n   } else if (key[0] == '0' && key[1] == 'x') {\n      /* keysym */\n      keycode = strtol(key, NULL, 0x10);\n   } else if ((st = getKeyCode(keystr)) == -1) {\n      MSG(\"Keys::parseKey unknown keyname: %s\\n\", keystr);\n   } else {\n      keycode = st;\n   }\n   dFree(keystr);\n\n   // Set binding\n   if (keycode) {\n      delKeyCmd(keycode, keymod);\n      if (symcode != KEYS_NOP) {\n         KeyBinding_t *node = dNew(KeyBinding_t, 1);\n         node->name = dStrdup(commandName);\n         node->cmd = symcode;\n         node->modifier = keymod;\n         node->key = keycode;\n         dList_insert_sorted(bindings, node, nodeByKeyCmp);\n         _MSG(\"parseKey: Adding key=%d, mod=%d\\n\", node->key, node->modifier);\n      }\n   }\n}\n\n/*\n * Parse the keysrc.\n */\nvoid Keys::parse(FILE *fp)\n{\n   char *line, *keycomb, *command;\n   int st, lineno = 1;\n\n   // scan the file line by line\n   while ((line = dGetline(fp)) != NULL) {\n      st = dParser_parse_rc_line(&line, &keycomb, &command);\n\n      if (st == 0) {\n         _MSG(\"Keys::parse: keycomb=%s, command=%s\\n\", keycomb, command);\n         parseKey(keycomb, command);\n      } else if (st < 0) {\n         MSG(\"Keys::parse: Syntax error in keysrc line %d: \"\n             \"keycomb=\\\"%s\\\" command=\\\"%s\\\"\\n\", lineno, keycomb, command);\n      }\n\n      dFree(line);\n      ++lineno;\n   }\n   fclose(fp);\n}\n"
  },
  {
    "path": "src/keys.hh",
    "content": "/*\n * Key parser\n *\n * Copyright (C) 2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef __KEYS_HH__\n#define __KEYS_HH__\n\n\ntypedef enum {\n   KEYS_INVALID = -1,\n   KEYS_NOP,   /* No operation bound */\n   KEYS_OPEN,\n   KEYS_NEW_WINDOW,\n   KEYS_NEW_TAB,\n   KEYS_LEFT_TAB,\n   KEYS_RIGHT_TAB,\n   KEYS_CLOSE_TAB,\n   KEYS_FIRST_TAB,\n   KEYS_LAST_TAB,\n   KEYS_FIND,\n   KEYS_WEBSEARCH,\n   KEYS_BOOKMARKS,\n   KEYS_RELOAD,\n   KEYS_STOP,\n   KEYS_SAVE,\n   KEYS_HIDE_PANELS,\n   KEYS_FILE_MENU,\n   KEYS_CLOSE_ALL,\n   KEYS_BACK,\n   KEYS_FORWARD,\n   KEYS_GOTO,\n   KEYS_HOME,\n   KEYS_VIEW_SOURCE,\n   KEYS_SCREEN_UP,\n   KEYS_SCREEN_DOWN,\n   KEYS_SCREEN_LEFT,\n   KEYS_SCREEN_RIGHT,\n   KEYS_LINE_UP,\n   KEYS_LINE_DOWN,\n   KEYS_LEFT,\n   KEYS_RIGHT,\n   KEYS_TOP,\n   KEYS_BOTTOM\n} KeysCommand_t;\n\nclass Keys {\nprivate:\n   static int nodeByKeyCmp(const void *node, const void *key);\n   static void delKeyCmd(int key, int mod);\n   static KeysCommand_t getCmdCode(const char *symbolName);\n   static int getKeyCode(char *keyName);\n   static int getModifier(char *modifierName);\n   static void parseKey(char *key, char *symbol);\npublic:\n   static void init();\n   static void free();\n   static void parse(FILE *fp);\n   static KeysCommand_t getKeyCmd(void);\n   static int getShortcut(KeysCommand_t cmd);\n};\n\n\n#endif /* __KEYS_HH__ */\n"
  },
  {
    "path": "src/keysrc",
    "content": "# keysrc\n# Sample dillo key bindings file.\n#\n# The format is: \"key = action\" or \"<modifier>key = action\".\n# Lines that begin with a '#' are comments.\n# The commented-out bindings below show the defaults built into Dillo.\n#\n# Modifiers recognized: \"Shift\", \"Ctrl\", \"Alt\", \"Meta\".\n# (OS X: Use \"Meta\" for Command)\n#\n# Key names recognized: \"Backspace\", \"Delete\", \"Down\", \"End\", \"Esc\",\n# \"F1\" through \"F12\", \"Home\", \"Insert\", \"Left\", \"Menu\", \"PageDown\", \"PageUp\",\n# \"Print\", \"Return\", \"Right\", \"Space\", \"Tab\", \"Up\".\n#\n# Multimedia keys: \"Back\", \"Favorites\", \"Forward\", \"HomePage\", \"Mail\",\n# \"MediaNext\", \"MediaPlay\", \"MediaPrev\", \"MediaStop\", \"Refresh\", \"Search\",\n# \"Sleep\", \"Stop\", \"VolumeDown\", \"VolumeMute\", VolumeUp\".\n#\n# If Dillo is running under X11, keys whose names are not recognized can\n# be specified using their keysym value in hexadecimal. Use xev to get\n# the keysym. Example rule: \"0x1008ff27 = forward\".\n#\n# The action \"nop\" (no operation) can be used to remove a binding.\n\n# \"open\" lets you browse your local files for one to open.\n#<ctrl>o = open\n\n# \"new-window\" opens a new browser window.\n#<ctrl>n = new-window\n\n# \"new-tab\" opens a new tab in the current browser window.\n#<ctrl>t = new-tab\n\n# \"close-tab\" closes the current tab.\n# Note that this closes the browser window if there is only one tab.\n#<ctrl>w = close-tab\n\n# \"close-all\" closes all tabs/windows and exits.\n#<ctrl>q = close-all\n\n# \"left-tab\" and \"right-tab\" switch to the left/right of the current tab.\n# <ctrl><shift>tab = left-tab\n# <ctrl>PageUp = left-tab\n# <ctrl>tab = right-tab\n# <ctrl>PageDown = right-tab\n\n# \"back\" and \"forward\" move back/forward through the browser history.\n#backspace = back\n#<shift>backspace = forward\n#, = back\n#. = forward\n\n# \"reload\" the current page.\n#<ctrl>r = reload\n\n# \"home\" goes to the homepage that you set in your dillorc.\n#h = home\n\n# \"find\" lets you search for a text string on the current page.\n#<ctrl>f = find\n#/=find\n\n# \"hide-panels\" hides the findbar if present, control panels if not.\n<ctrl>h = hide-panels\n\n# \"websearch\" lets you send a text string to the search engine that you\n# set in your dillorc.\n#s = websearch\n\n# go to your \"bookmarks\".\nb = bookmarks\n\n# \"file-menu\" pops up the file menu.\n#<alt>f = file-menu\n\n# \"view-source\" displays the page source.\n#<ctrl>u = view-source\n\n# \"goto\" goes to the location bar at the top of the window.\n#<ctrl>l = goto\n\n# \"stop\" loading the page.\n#(stop has no default binding)\nesc = stop\n\n# \"save\" the current page.\n#<ctrl>s = save\n\n#--------------------------------------------------------------------\n#                     MOTION COMMANDS\n#--------------------------------------------------------------------\n\n#pageup = screen-up\n#<shift>space = screen-up\n\n#pagedown = screen-down\n#space = screen-down\n\n#(screen-left has no default binding)\n\n#(screen-right has no default binding)\n\n#up = line-up\n\n#down = line-down\n\n#left = left\n\n#right = right\n\n#home = top\n\n#end = bottom\n\n"
  },
  {
    "path": "src/klist.c",
    "content": "/*\n * File: klist.c\n *\n * Copyright 2001-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * A simple ADT for Key-Data pairs\n *\n * NOTE: this ADT is not perfect. The possibility of holding a Key, after\n * its data has been removed, long enough for the key-counter to reset and\n * reuse the same key is very low, but EXISTS. So, the responsibility\n * remains with the caller.\n */\n\n#include \"klist.h\"\n\n\n/*\n * Compare function for searching data by its key\n */\nstatic int Klist_node_by_key_cmp(const void *Node, const void *key)\n{\n   return ((KlistNode_t *)Node)->Key - VOIDP2INT(key);\n}\n\n/*\n * Compare function for searching data by node\n */\nstatic int Klist_node_by_node_cmp(const void *Node1, const void *Node2)\n{\n   return ((KlistNode_t *)Node1)->Key - ((KlistNode_t *)Node2)->Key;\n}\n\n/*\n * Return the data pointer for a given Key (or NULL if not found)\n */\nvoid *a_Klist_get_data(Klist_t *Klist, int Key)\n{\n   void *aux;\n\n   if (!Klist)\n      return NULL;\n   aux = dList_find_sorted(Klist->List, INT2VOIDP(Key), Klist_node_by_key_cmp);\n   return (aux) ? ((KlistNode_t*)aux)->Data : NULL;\n}\n\n/*\n * Insert a data pointer and return a key for it.\n */\nint a_Klist_insert(Klist_t **Klist, void *Data)\n{\n   KlistNode_t *Node;\n\n   if (!*Klist) {\n      (*Klist) = dNew(Klist_t, 1);\n      (*Klist)->List = dList_new(32);\n      (*Klist)->Clean = 1;\n      (*Klist)->Counter = 0;\n   }\n\n   /* This avoids repeated keys at the same time */\n   do {\n      if (++((*Klist)->Counter) == 0) {\n         (*Klist)->Counter = 1;\n         (*Klist)->Clean = 0;\n      }\n   } while (!((*Klist)->Clean) &&\n            a_Klist_get_data((*Klist), (*Klist)->Counter));\n\n   Node = dNew(KlistNode_t, 1);\n   Node->Key = (*Klist)->Counter;\n   Node->Data = Data;\n   dList_insert_sorted((*Klist)->List, Node, Klist_node_by_node_cmp);\n   return (*Klist)->Counter;\n}\n\n/*\n * Remove data by Key\n */\nvoid a_Klist_remove(Klist_t *Klist, int Key)\n{\n   void *data;\n\n   data = dList_find_sorted(Klist->List, INT2VOIDP(Key),Klist_node_by_key_cmp);\n   if (data) {\n      dList_remove(Klist->List, data);\n      dFree(data);\n   }\n   if (dList_length(Klist->List) == 0)\n      Klist->Clean = 1;\n}\n\n/*\n * Return the number of elements in the Klist\n */\nint a_Klist_length(Klist_t *Klist)\n{\n   return dList_length(Klist->List);\n}\n\n/*\n * Free a Klist\n */\nvoid a_Klist_free(Klist_t **KlistPtr)\n{\n   void *node;\n   Klist_t *Klist = *KlistPtr;\n\n   if (!Klist)\n      return;\n\n   while (dList_length(Klist->List) > 0) {\n      node = dList_nth_data(Klist->List, 0);\n      dList_remove_fast(Klist->List, node);\n      dFree(node);\n   }\n   dList_free(Klist->List);\n   dFree(Klist);\n   *KlistPtr = NULL;\n}\n\n"
  },
  {
    "path": "src/klist.h",
    "content": "#ifndef __KLIST_H__\n#define __KLIST_H__\n\n#include \"../dlib/dlib.h\"\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\ntypedef struct {\n   int Key;        /* primary key */\n   void *Data;     /* data reference */\n} KlistNode_t;\n\ntypedef struct {\n   Dlist *List;\n   int Clean;      /* check flag */\n   int Counter;    /* counter (for making keys) */\n} Klist_t;\n\n\n/*\n * Function prototypes\n */\nvoid*     a_Klist_get_data(Klist_t *Klist, int Key);\nint       a_Klist_insert(Klist_t **Klist, void *Data);\nvoid      a_Klist_remove(Klist_t *Klist, int Key);\nint       a_Klist_length(Klist_t *Klist);\nvoid      a_Klist_free(Klist_t **Klist);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __KLIST_H__ */\n"
  },
  {
    "path": "src/list.h",
    "content": "/*\n * Fast list methods\n * Feb 2000  --Jcid\n *\n */\n\n#ifndef __LIST_H__\n#define __LIST_H__\n\n/*\n * a_List_resize()\n *\n * Make sure there's space for 'num_items' items within the list\n * (First, allocate an 'alloc_step' sized chunk, after that, double the\n *  list size --to make it faster)\n */\n#define a_List_resize(list,num_items,alloc_step) \\\n   if (!list) { \\\n      list = dMalloc(alloc_step * sizeof(*list)); \\\n   } \\\n   if (num_items >= alloc_step){ \\\n      while ( num_items >= alloc_step ) \\\n         alloc_step <<= 1; \\\n      list = dRealloc(list, alloc_step * sizeof(*list)); \\\n   }\n\n\n/*\n * a_List_add()\n *\n * Make sure there's space for one more item within the list.\n */\n#define a_List_add(list,num_items,alloc_step) \\\n   a_List_resize(list,num_items,alloc_step)\n\n\n/*\n * a_List_remove()\n *\n * Quickly remove an item from the list\n * ==> We preserve relative position, but not the element index <==\n */\n#define a_List_remove(list, item, num_items) \\\n   if (list && item < num_items) { \\\n      list[item] = list[--num_items]; \\\n   }\n\n\n#endif /* __LIST_H__ */\n"
  },
  {
    "path": "src/md5.c",
    "content": "/*\n * md5.c was taken from \"RFC1321-based (RSA-free) MD5 library\" by L. Peter\n * Deutsch at http://sourceforge.net/projects/libmd5-rfc/ in October 2011.\n *\n * The code was not modified when integrated into the Dillo project, but you\n * should check the source repository to be sure that there have not been\n * modifications since this notice.\n */\n\n/*\n  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any damages\n  arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any purpose,\n  including commercial applications, and to alter it and redistribute it\n  freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you must not\n     claim that you wrote the original software. If you use this software\n     in a product, an acknowledgment in the product documentation would be\n     appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and must not be\n     misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source distribution.\n\n  L. Peter Deutsch\n  ghost@aladdin.com\n\n */\n/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */\n/*\n  Independent implementation of MD5 (RFC 1321).\n\n  This code implements the MD5 Algorithm defined in RFC 1321, whose\n  text is available at\n\thttp://www.ietf.org/rfc/rfc1321.txt\n  The code is derived from the text of the RFC, including the test suite\n  (section A.5) but excluding the rest of Appendix A.  It does not include\n  any code or documentation that is identified in the RFC as being\n  copyrighted.\n\n  The original and principal author of md5.c is L. Peter Deutsch\n  <ghost@aladdin.com>.  Other authors are noted in the change history\n  that follows (in reverse chronological order):\n\n  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order\n\teither statically or dynamically; added missing #include <string.h>\n\tin library.\n  2002-03-11 lpd Corrected argument list for main(), and added int return\n\ttype, in test program and T value program.\n  2002-02-21 lpd Added missing #include <stdio.h> in test program.\n  2000-07-03 lpd Patched to eliminate warnings about \"constant is\n\tunsigned in ANSI C, signed in traditional\"; made test program\n\tself-checking.\n  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.\n  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).\n  1999-05-03 lpd Original version.\n */\n\n#include \"md5.h\"\n#include <string.h>\n\n#undef BYTE_ORDER\t/* 1 = big-endian, -1 = little-endian, 0 = unknown */\n#ifdef ARCH_IS_BIG_ENDIAN\n#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)\n#else\n#  define BYTE_ORDER 0\n#endif\n\n#define T_MASK ((md5_word_t)~0)\n#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)\n#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)\n#define T3    0x242070db\n#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)\n#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)\n#define T6    0x4787c62a\n#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)\n#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)\n#define T9    0x698098d8\n#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)\n#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)\n#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)\n#define T13    0x6b901122\n#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)\n#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)\n#define T16    0x49b40821\n#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)\n#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)\n#define T19    0x265e5a51\n#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)\n#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)\n#define T22    0x02441453\n#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)\n#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)\n#define T25    0x21e1cde6\n#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)\n#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)\n#define T28    0x455a14ed\n#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)\n#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)\n#define T31    0x676f02d9\n#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)\n#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)\n#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)\n#define T35    0x6d9d6122\n#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)\n#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)\n#define T38    0x4bdecfa9\n#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)\n#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)\n#define T41    0x289b7ec6\n#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)\n#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)\n#define T44    0x04881d05\n#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)\n#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)\n#define T47    0x1fa27cf8\n#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)\n#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)\n#define T50    0x432aff97\n#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)\n#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)\n#define T53    0x655b59c3\n#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)\n#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)\n#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)\n#define T57    0x6fa87e4f\n#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)\n#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)\n#define T60    0x4e0811a1\n#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)\n#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)\n#define T63    0x2ad7d2bb\n#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)\n\n\nstatic void\nmd5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)\n{\n    md5_word_t\n\ta = pms->abcd[0], b = pms->abcd[1],\n\tc = pms->abcd[2], d = pms->abcd[3];\n    md5_word_t t;\n#if BYTE_ORDER > 0\n    /* Define storage only for big-endian CPUs. */\n    md5_word_t X[16];\n#else\n    /* Define storage for little-endian or both types of CPUs. */\n    md5_word_t xbuf[16];\n    const md5_word_t *X;\n#endif\n\n    {\n#if BYTE_ORDER == 0\n\t/*\n\t * Determine dynamically whether this is a big-endian or\n\t * little-endian machine, since we can use a more efficient\n\t * algorithm on the latter.\n\t */\n\tstatic const int w = 1;\n\n\tif (*((const md5_byte_t *)&w)) /* dynamic little-endian */\n#endif\n#if BYTE_ORDER <= 0\t\t/* little-endian */\n\t{\n\t    /*\n\t     * On little-endian machines, we can process properly aligned\n\t     * data without copying it.\n\t     */\n\t    if (!((data - (const md5_byte_t *)0) & 3)) {\n\t\t/* data are properly aligned */\n\t\tX = (const md5_word_t *)data;\n\t    } else {\n\t\t/* not aligned */\n\t\tmemcpy(xbuf, data, 64);\n\t\tX = xbuf;\n\t    }\n\t}\n#endif\n#if BYTE_ORDER == 0\n\telse\t\t\t/* dynamic big-endian */\n#endif\n#if BYTE_ORDER >= 0\t\t/* big-endian */\n\t{\n\t    /*\n\t     * On big-endian machines, we must arrange the bytes in the\n\t     * right order.\n\t     */\n\t    const md5_byte_t *xp = data;\n\t    int i;\n\n#  if BYTE_ORDER == 0\n\t    X = xbuf;\t\t/* (dynamic only) */\n#  else\n#    define xbuf X\t\t/* (static only) */\n#  endif\n\t    for (i = 0; i < 16; ++i, xp += 4)\n\t\txbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);\n\t}\n#endif\n    }\n\n#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))\n\n    /* Round 1. */\n    /* Let [abcd k s i] denote the operation\n       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */\n#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))\n#define SET(a, b, c, d, k, s, Ti)\\\n  t = a + F(b,c,d) + X[k] + Ti;\\\n  a = ROTATE_LEFT(t, s) + b\n    /* Do the following 16 operations. */\n    SET(a, b, c, d,  0,  7,  T1);\n    SET(d, a, b, c,  1, 12,  T2);\n    SET(c, d, a, b,  2, 17,  T3);\n    SET(b, c, d, a,  3, 22,  T4);\n    SET(a, b, c, d,  4,  7,  T5);\n    SET(d, a, b, c,  5, 12,  T6);\n    SET(c, d, a, b,  6, 17,  T7);\n    SET(b, c, d, a,  7, 22,  T8);\n    SET(a, b, c, d,  8,  7,  T9);\n    SET(d, a, b, c,  9, 12, T10);\n    SET(c, d, a, b, 10, 17, T11);\n    SET(b, c, d, a, 11, 22, T12);\n    SET(a, b, c, d, 12,  7, T13);\n    SET(d, a, b, c, 13, 12, T14);\n    SET(c, d, a, b, 14, 17, T15);\n    SET(b, c, d, a, 15, 22, T16);\n#undef SET\n\n     /* Round 2. */\n     /* Let [abcd k s i] denote the operation\n          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */\n#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))\n#define SET(a, b, c, d, k, s, Ti)\\\n  t = a + G(b,c,d) + X[k] + Ti;\\\n  a = ROTATE_LEFT(t, s) + b\n     /* Do the following 16 operations. */\n    SET(a, b, c, d,  1,  5, T17);\n    SET(d, a, b, c,  6,  9, T18);\n    SET(c, d, a, b, 11, 14, T19);\n    SET(b, c, d, a,  0, 20, T20);\n    SET(a, b, c, d,  5,  5, T21);\n    SET(d, a, b, c, 10,  9, T22);\n    SET(c, d, a, b, 15, 14, T23);\n    SET(b, c, d, a,  4, 20, T24);\n    SET(a, b, c, d,  9,  5, T25);\n    SET(d, a, b, c, 14,  9, T26);\n    SET(c, d, a, b,  3, 14, T27);\n    SET(b, c, d, a,  8, 20, T28);\n    SET(a, b, c, d, 13,  5, T29);\n    SET(d, a, b, c,  2,  9, T30);\n    SET(c, d, a, b,  7, 14, T31);\n    SET(b, c, d, a, 12, 20, T32);\n#undef SET\n\n     /* Round 3. */\n     /* Let [abcd k s t] denote the operation\n          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */\n#define H(x, y, z) ((x) ^ (y) ^ (z))\n#define SET(a, b, c, d, k, s, Ti)\\\n  t = a + H(b,c,d) + X[k] + Ti;\\\n  a = ROTATE_LEFT(t, s) + b\n     /* Do the following 16 operations. */\n    SET(a, b, c, d,  5,  4, T33);\n    SET(d, a, b, c,  8, 11, T34);\n    SET(c, d, a, b, 11, 16, T35);\n    SET(b, c, d, a, 14, 23, T36);\n    SET(a, b, c, d,  1,  4, T37);\n    SET(d, a, b, c,  4, 11, T38);\n    SET(c, d, a, b,  7, 16, T39);\n    SET(b, c, d, a, 10, 23, T40);\n    SET(a, b, c, d, 13,  4, T41);\n    SET(d, a, b, c,  0, 11, T42);\n    SET(c, d, a, b,  3, 16, T43);\n    SET(b, c, d, a,  6, 23, T44);\n    SET(a, b, c, d,  9,  4, T45);\n    SET(d, a, b, c, 12, 11, T46);\n    SET(c, d, a, b, 15, 16, T47);\n    SET(b, c, d, a,  2, 23, T48);\n#undef SET\n\n     /* Round 4. */\n     /* Let [abcd k s t] denote the operation\n          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */\n#define I(x, y, z) ((y) ^ ((x) | ~(z)))\n#define SET(a, b, c, d, k, s, Ti)\\\n  t = a + I(b,c,d) + X[k] + Ti;\\\n  a = ROTATE_LEFT(t, s) + b\n     /* Do the following 16 operations. */\n    SET(a, b, c, d,  0,  6, T49);\n    SET(d, a, b, c,  7, 10, T50);\n    SET(c, d, a, b, 14, 15, T51);\n    SET(b, c, d, a,  5, 21, T52);\n    SET(a, b, c, d, 12,  6, T53);\n    SET(d, a, b, c,  3, 10, T54);\n    SET(c, d, a, b, 10, 15, T55);\n    SET(b, c, d, a,  1, 21, T56);\n    SET(a, b, c, d,  8,  6, T57);\n    SET(d, a, b, c, 15, 10, T58);\n    SET(c, d, a, b,  6, 15, T59);\n    SET(b, c, d, a, 13, 21, T60);\n    SET(a, b, c, d,  4,  6, T61);\n    SET(d, a, b, c, 11, 10, T62);\n    SET(c, d, a, b,  2, 15, T63);\n    SET(b, c, d, a,  9, 21, T64);\n#undef SET\n\n     /* Then perform the following additions. (That is increment each\n        of the four registers by the value it had before this block\n        was started.) */\n    pms->abcd[0] += a;\n    pms->abcd[1] += b;\n    pms->abcd[2] += c;\n    pms->abcd[3] += d;\n}\n\nvoid\nmd5_init(md5_state_t *pms)\n{\n    pms->count[0] = pms->count[1] = 0;\n    pms->abcd[0] = 0x67452301;\n    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;\n    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;\n    pms->abcd[3] = 0x10325476;\n}\n\nvoid\nmd5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)\n{\n    const md5_byte_t *p = data;\n    int left = nbytes;\n    int offset = (pms->count[0] >> 3) & 63;\n    md5_word_t nbits = (md5_word_t)(nbytes << 3);\n\n    if (nbytes <= 0)\n\treturn;\n\n    /* Update the message length. */\n    pms->count[1] += nbytes >> 29;\n    pms->count[0] += nbits;\n    if (pms->count[0] < nbits)\n\tpms->count[1]++;\n\n    /* Process an initial partial block. */\n    if (offset) {\n\tint copy = (offset + nbytes > 64 ? 64 - offset : nbytes);\n\n\tmemcpy(pms->buf + offset, p, copy);\n\tif (offset + copy < 64)\n\t    return;\n\tp += copy;\n\tleft -= copy;\n\tmd5_process(pms, pms->buf);\n    }\n\n    /* Process full blocks. */\n    for (; left >= 64; p += 64, left -= 64)\n\tmd5_process(pms, p);\n\n    /* Process a final partial block. */\n    if (left)\n\tmemcpy(pms->buf, p, left);\n}\n\nvoid\nmd5_finish(md5_state_t *pms, md5_byte_t digest[16])\n{\n    static const md5_byte_t pad[64] = {\n\t0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n\t0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n    };\n    md5_byte_t data[8];\n    int i;\n\n    /* Save the length before padding. */\n    for (i = 0; i < 8; ++i)\n\tdata[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));\n    /* Pad to 56 bytes mod 64. */\n    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);\n    /* Append the length. */\n    md5_append(pms, data, 8);\n    for (i = 0; i < 16; ++i)\n\tdigest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));\n}\n"
  },
  {
    "path": "src/md5.h",
    "content": "/*\n * md5.h was taken from \"RFC1321-based (RSA-free) MD5 library\" by L. Peter\n * Deutsch at http://sourceforge.net/projects/libmd5-rfc/ in October 2011.\n *\n * The code was not modified when integrated into the Dillo project, but you\n * should check the source repository to be sure that there have not been\n * modifications since this notice.\n */\n\n/*\n  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any damages\n  arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any purpose,\n  including commercial applications, and to alter it and redistribute it\n  freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you must not\n     claim that you wrote the original software. If you use this software\n     in a product, an acknowledgment in the product documentation would be\n     appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and must not be\n     misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source distribution.\n\n  L. Peter Deutsch\n  ghost@aladdin.com\n\n */\n/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */\n/*\n  Independent implementation of MD5 (RFC 1321).\n\n  This code implements the MD5 Algorithm defined in RFC 1321, whose\n  text is available at\n\thttp://www.ietf.org/rfc/rfc1321.txt\n  The code is derived from the text of the RFC, including the test suite\n  (section A.5) but excluding the rest of Appendix A.  It does not include\n  any code or documentation that is identified in the RFC as being\n  copyrighted.\n\n  The original and principal author of md5.h is L. Peter Deutsch\n  <ghost@aladdin.com>.  Other authors are noted in the change history\n  that follows (in reverse chronological order):\n\n  2002-04-13 lpd Removed support for non-ANSI compilers; removed\n\treferences to Ghostscript; clarified derivation from RFC 1321;\n\tnow handles byte order either statically or dynamically.\n  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.\n  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);\n\tadded conditionalization for C++ compilation from Martin\n\tPurschke <purschke@bnl.gov>.\n  1999-05-03 lpd Original version.\n */\n\n#ifndef md5_INCLUDED\n#  define md5_INCLUDED\n\n/*\n * This package supports both compile-time and run-time determination of CPU\n * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be\n * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is\n * defined as non-zero, the code will be compiled to run only on big-endian\n * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to\n * run on either big- or little-endian CPUs, but will run slightly less\n * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.\n */\n\ntypedef unsigned char md5_byte_t; /* 8-bit byte */\ntypedef unsigned int md5_word_t; /* 32-bit word */\n\n/* Define the state of the MD5 Algorithm. */\ntypedef struct md5_state_s {\n    md5_word_t count[2];\t/* message length in bits, lsw first */\n    md5_word_t abcd[4];\t\t/* digest buffer */\n    md5_byte_t buf[64];\t\t/* accumulate block */\n} md5_state_t;\n\n#ifdef __cplusplus\nextern \"C\" \n{\n#endif\n\n/* Initialize the algorithm. */\nvoid md5_init(md5_state_t *pms);\n\n/* Append a string to the message. */\nvoid md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);\n\n/* Finish the message and return the digest. */\nvoid md5_finish(md5_state_t *pms, md5_byte_t digest[16]);\n\n#ifdef __cplusplus\n}  /* end extern \"C\" */\n#endif\n\n#endif /* md5_INCLUDED */\n"
  },
  {
    "path": "src/menu.cc",
    "content": "/*\n * File: menu.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n// Functions/Methods for menus\n\n#include <FL/Fl.H>\n#include <FL/Fl_Menu_Item.H>\n\n#include \"lout/misc.hh\"    /* SimpleVector */\n#include \"msg.h\"\n#include \"menu.hh\"\n#include \"uicmd.hh\"\n#include \"history.h\"\n#include \"html.hh\"\n#include \"ui.hh\" // for (UI *)\n#include \"keys.hh\"\n#include \"timeout.hh\"\n\n/*\n * Local data types\n */\n\ntypedef struct {\n   const char *title;\n   const Fl_Menu_Item *picked;\n   const Fl_Menu_Item *menu;\n} Menu_popup_data_t;\n\n/*\n * Local data\n */\n\n// (This data can be encapsulated inside a class for each popup, but\n//  as popups are modal, there's no need).\nstatic DilloUrl *popup_url = NULL;\n// Weak reference to the popup's bw\nstatic BrowserWindow *popup_bw = NULL;\nstatic void *popup_form = NULL;\n// Where to place the popup\nstatic int popup_x, popup_y;\n// History popup direction (-1 = back, 1 = forward).\nstatic int history_direction = -1;\n// History popup, list of URL-indexes.\nstatic int *history_list = NULL;\n\n\n//--------------------------------------------------------------------------\nstatic void Menu_nop_cb(Fl_Widget*, void*)\n{\n}\n\n/*\n * Static function for File menu callbacks.\n */\nstatic void filemenu_cb(Fl_Widget*, void *data)\n{\n   if (strcmp((char*)data, \"nw\") == 0) {\n      a_UIcmd_open_url_nw(popup_bw, NULL);\n   } else if (strcmp((char*)data, \"nt\") == 0) {\n      a_UIcmd_open_url_nt(popup_bw, NULL, 1);\n   } else if (strcmp((char*)data, \"of\") == 0) {\n      a_UIcmd_open_file(popup_bw);\n   } else if (strcmp((char*)data, \"ou\") == 0) {\n      a_UIcmd_focus_location(popup_bw);\n   } else if (strcmp((char*)data, \"cw\") == 0) {\n      a_Timeout_add(0.0, a_UIcmd_close_bw, popup_bw);\n   } else if (strcmp((char*)data, \"ed\") == 0) {\n      a_Timeout_add(0.0, a_UIcmd_close_all_bw, NULL);\n   }\n}\n\n\nstatic void Menu_copy_urlstr_cb(Fl_Widget*, void *user_data)\n{\n   if (user_data) {\n      DilloUrl *url = (DilloUrl *)user_data ;\n      a_UIcmd_copy_urlstr(popup_bw, URL_STR(url));\n   }\n}\n\n/*\n * Open URL\n */\nstatic void Menu_open_url_cb(Fl_Widget*, void *user_data)\n{\n   DilloUrl *url = (DilloUrl *)user_data;\n   _MSG(\"Open URL cb: click! :-)\\n\");\n   a_UIcmd_open_url(popup_bw, url);\n}\n\n/*\n * Open URL in new window\n */\nstatic void Menu_open_url_nw_cb(Fl_Widget*, void *user_data)\n{\n   DilloUrl *url = (DilloUrl *)user_data;\n   _MSG(\"Open URL in new window cb: click! :-)\\n\");\n   a_UIcmd_open_url_nw(popup_bw, url);\n}\n\n/*\n * Open URL in media player\n */\nstatic void Menu_open_url_mp_cb(Fl_Widget*, void *user_data)\n{\n   DilloUrl *url = (DilloUrl *)user_data;\n\n   int size = 0;\n   char *str;\n\n   size += strlen(prefs.media_player);\n   size += sizeof(\" \\\"\");\n   size += strlen(URL_STR_(url));\n   size += sizeof(\"\\\" > /dev/null 2>&1 &\");\n   size += 1;\n\n   str = (char *) malloc(size);\n\n   if(!str) {\n     MSG_WARN (\"Error: can not allocate media player cmd string.\");\n     return;\n   }\n   \n   strcpy (str, prefs.media_player);\n   strcat(str, \" \\\"\");\n   strcat (str, URL_STR_(url));\n   strcat(str, \"\\\" > /dev/null 2>&1 &\");\n   puts (str);\n   system(str);\n\n   free(str);\n}\n\n/*\n * Open URL in new Tab\n */\nstatic void Menu_open_url_nt_cb(Fl_Widget*, void *user_data)\n{\n   DilloUrl *url = (DilloUrl *)user_data;\n   int focus = prefs.focus_new_tab ? 1 : 0;\n   if (Fl::event_state(FL_SHIFT)) focus = !focus;\n   a_UIcmd_open_url_nt(popup_bw, url, focus);\n}\n\n/*\n * Add bookmark\n */\nstatic void Menu_add_bookmark_cb(Fl_Widget*, void *user_data)\n{\n   DilloUrl *url = (DilloUrl *)user_data;\n   a_UIcmd_add_bookmark(popup_bw, url);\n}\n\n/*\n * Find text\n */\nstatic void Menu_find_text_cb(Fl_Widget*, void*)\n{\n   ((UI *)popup_bw->ui)->findbar_toggle(1);\n}\n\n/*\n * Save link\n */\nstatic void Menu_save_link_cb(Fl_Widget*, void *user_data)\n{\n   DilloUrl *url = (DilloUrl *)user_data;\n   a_UIcmd_save_link(popup_bw, url);\n}\n\n/*\n * Save current page\n */\nstatic void Menu_save_page_cb(Fl_Widget*, void*)\n{\n   a_UIcmd_save(popup_bw);\n}\n\n/*\n * View current page source\n */\nstatic void Menu_view_page_source_cb(Fl_Widget*, void *user_data)\n{\n   DilloUrl *url = (DilloUrl *)user_data;\n   a_UIcmd_view_page_source(popup_bw, url);\n}\n\n/*\n * View current page's bugs\n */\nstatic void Menu_view_page_bugs_cb(Fl_Widget*, void*)\n{\n   a_UIcmd_view_page_bugs(popup_bw);\n}\n\n/*\n * Load images on current page that match URL pattern\n */\nstatic void Menu_load_images_cb(Fl_Widget*, void *user_data)\n{\n   DilloUrl *page_url = (DilloUrl *) user_data;\n   void *doc = a_Bw_get_url_doc(popup_bw, page_url);\n\n   if (doc)\n      a_Html_load_images(doc, popup_url);\n}\n\n/*\n * Submit form\n */\nstatic void Menu_form_submit_cb(Fl_Widget*, void*)\n{\n   void *doc = a_Bw_get_url_doc(popup_bw, popup_url);\n\n   if (doc)\n      a_Html_form_submit(doc, popup_form);\n}\n\n/*\n * Reset form\n */\nstatic void Menu_form_reset_cb(Fl_Widget*, void*)\n{\n   void *doc = a_Bw_get_url_doc(popup_bw, popup_url);\n\n   if (doc)\n      a_Html_form_reset(doc, popup_form);\n}\n\n/*\n * Toggle display of 'hidden' form controls.\n */\nstatic void Menu_form_hiddens_cb(Fl_Widget*, void *user_data)\n{\n   bool visible = *((bool *) user_data);\n   void *doc = a_Bw_get_url_doc(popup_bw, popup_url);\n\n   if (doc)\n      a_Html_form_display_hiddens(doc, popup_form, !visible);\n}\n\nstatic void Menu_stylesheet_cb(Fl_Widget*, void *vUrl)\n{\n   int mb = Fl::event_button();\n   const DilloUrl *url = (const DilloUrl *) vUrl;\n\n   if (mb == 1) {\n      a_UIcmd_open_url(popup_bw, url);\n   } else if (mb == 2) {\n      if (prefs.middle_click_opens_new_tab) {\n         int focus = prefs.focus_new_tab ? 1 : 0;\n         if (Fl::event_state(FL_SHIFT)) focus = !focus;\n         a_UIcmd_open_url_nt(popup_bw, url, focus);\n      } else {\n         a_UIcmd_open_url_nw(popup_bw, url);\n      }\n   }\n}\n\nstatic void Menu_bugmeter_validate(const char *validator_url)\n{\n   if (popup_url &&\n       dStrAsciiCasecmp(URL_SCHEME(popup_url), \"dpi\")) {\n      const char *popup_str = URL_STR(popup_url),\n                 *ptr = strrchr(popup_str, '#');\n      char *no_fragment = ptr ? dStrndup(popup_str, ptr - popup_str)\n                              : dStrdup(popup_str);\n      char *encoded = a_Url_encode_hex_str(no_fragment);\n      Dstr *dstr = dStr_sized_new(128);\n\n      dStr_sprintf(dstr, validator_url, encoded);\n      a_UIcmd_open_urlstr(popup_bw, dstr->str);\n      dStr_free(dstr, 1);\n      dFree(encoded);\n      dFree(no_fragment);\n   }\n}\n\n/*\n * Validate URL with the W3C\n */\nstatic void Menu_bugmeter_validate_w3c_cb(Fl_Widget*, void*)\n{\n   Menu_bugmeter_validate(\"http://validator.w3.org/check?uri=%s\");\n}\n\n/*\n * Validate URL with the WDG\n */\nstatic void Menu_bugmeter_validate_wdg_cb(Fl_Widget*, void*)\n{\n   Menu_bugmeter_validate(\n      \"http://www.htmlhelp.org/cgi-bin/validate.cgi?url=%s&warnings=yes\");\n}\n\n/*\n * Show info page for the bug meter\n */\nstatic void Menu_bugmeter_about_cb(Fl_Widget*, void*)\n{\n   a_UIcmd_open_urlstr(popup_bw, \"https://dillo-browser.github.io/old/help/bug_meter.html\");\n}\n\n/*\n * Navigation History callback.\n * Go to selected URL.\n */\nstatic void Menu_history_cb(Fl_Widget*, void *data)\n{\n   int mb = Fl::event_button();\n   int offset = history_direction * VOIDP2INT(data);\n   const DilloUrl *url = a_History_get_url(history_list[VOIDP2INT(data)-1]);\n\n   if (mb == 1) {\n      a_UIcmd_nav_jump(popup_bw, offset, 0);\n   } else if (mb == 2) {\n      // Middle button, open in a new window/tab\n      if (prefs.middle_click_opens_new_tab) {\n         int focus = prefs.focus_new_tab ? 1 : 0;\n         if (Fl::event_state(FL_SHIFT)) focus = !focus;\n         a_UIcmd_open_url_nt(popup_bw, url, focus);\n      } else {\n         a_UIcmd_open_url_nw(popup_bw, url);\n      }\n   }\n}\n\n/*\n * Menus are popped-up from this timeout callback so the events\n * associated with the button are gone when it pops. This way we\n * avoid a segfault when a new page replaces the page that issued\n * the popup menu.\n */\nstatic void Menu_simple_popup_cb(void *data)\n{\n   const Fl_Menu_Item *m;\n\n   ((UI*)popup_bw->ui)->window()->cursor(FL_CURSOR_DEFAULT);\n\n   m = ((Fl_Menu_Item *)data)->popup(popup_x, popup_y);\n\n   if (m && m->callback())\n      m->do_callback((Fl_Widget *)data);\n   a_Timeout_remove();\n}\n\nstatic void Menu_popup_cb(void *data)\n{\n   const Fl_Menu_Item *picked;\n   Menu_popup_data_t *d = (Menu_popup_data_t *)data;\n\n   ((UI*)popup_bw->ui)->window()->cursor(FL_CURSOR_DEFAULT);\n\n   picked = d->menu->popup(popup_x, popup_y, d->title, d->picked);\n   if (picked) {\n      d->picked = picked;\n      if (picked->callback())\n         picked->do_callback((Fl_Widget *)(d->menu));\n   }\n   a_Timeout_remove();\n}\n\n/*\n * Page popup menu (construction & popup)\n */\nvoid a_Menu_page_popup(BrowserWindow *bw, const DilloUrl *url,\n                       bool_t has_bugs, void *v_cssUrls)\n{\n   lout::misc::SimpleVector <DilloUrl*> *cssUrls =\n                            (lout::misc::SimpleVector <DilloUrl*> *) v_cssUrls;\n   int j = 0;\n\n   static Fl_Menu_Item *stylesheets = NULL;\n   static Fl_Menu_Item pm[] = {\n      {\"View page source\", 0, Menu_view_page_source_cb,0,0,0,0,0,0},\n      {\"View page bugs\", 0, Menu_view_page_bugs_cb,0,0,0,0,0,0},\n      {\"View stylesheets\", 0, Menu_nop_cb,0,FL_SUBMENU_POINTER|FL_MENU_DIVIDER,\n       0,0,0,0},\n      {\"Bookmark this page\", 0,Menu_add_bookmark_cb,0,FL_MENU_DIVIDER,0,0,0,0},\n      {\"Find text\", 0, Menu_find_text_cb,0,0,0,0,0,0},\n      {\"Save page as...\", 0, Menu_save_page_cb,0,0,0,0,0,0},\n      {0,0,0,0,0,0,0,0,0}\n   };\n   static Menu_popup_data_t page_data = {\"Page menu\", NULL, pm};\n\n   popup_x = Fl::event_x();\n   popup_y = Fl::event_y();\n   popup_bw = bw;\n   a_Url_free(popup_url);\n   popup_url = a_Url_dup(url);\n\n   has_bugs == TRUE ? pm[1].activate() : pm[1].deactivate();\n\n   if (dStrAsciiCasecmp(URL_SCHEME(url), \"dpi\") == 0 &&\n       strncmp(URL_PATH(url), \"/vsource/\", 9) == 0)\n      pm[0].deactivate();\n   else {\n      pm[0].activate();\n      pm[0].user_data(popup_url);\n   }\n\n   if (stylesheets) {\n      while (stylesheets[j].text) {\n         dFree((char *) stylesheets[j].label());\n         a_Url_free((DilloUrl *) stylesheets[j].user_data());\n         j++;\n      }\n      delete [] stylesheets;\n      stylesheets = NULL;\n   }\n\n   if (cssUrls && cssUrls->size () > 0) {\n      stylesheets = new Fl_Menu_Item[cssUrls->size() + 1];\n      memset(stylesheets, '\\0', sizeof(Fl_Menu_Item[cssUrls->size() + 1]));\n\n      for (j = 0; j < cssUrls->size(); j++) {\n         DilloUrl *url = cssUrls->get(j);\n         const char *url_str = URL_STR(url);\n         const uint_t head_length = 30, tail_length = 40,\n                      url_len = strlen(url_str);\n         char *label;\n\n         if (url_len > head_length + tail_length + 3) {\n            /* trim long URLs when making the label */\n            char *url_head = dStrndup(url_str, head_length);\n            const char *url_tail = url_str + (url_len - tail_length);\n            label = dStrconcat(url_head, \"...\", url_tail, NULL);\n            dFree(url_head);\n         } else {\n            label = dStrdup(url_str);\n         }\n\n         stylesheets[j].label(FL_NORMAL_LABEL, label);\n         stylesheets[j].callback(Menu_stylesheet_cb, a_Url_dup(url));\n      }\n\n      pm[2].user_data(stylesheets);\n      pm[2].activate();\n   } else {\n      pm[2].deactivate();\n   }\n   pm[3].user_data(popup_url);\n\n   a_Timeout_add(0.0, Menu_popup_cb, (void*)&page_data);\n}\n\nstatic Fl_Menu_Item link_menu[] = {\n   {\"Open link in new tab\", 0, Menu_open_url_nt_cb,0,0,0,0,0,0},\n   {\"Open link in new window\", 0, Menu_open_url_nw_cb,0,FL_MENU_DIVIDER,0,0,0,0},\n   {\"Open link with media player\", 0, Menu_open_url_mp_cb,0,FL_MENU_DIVIDER,0,0,0,0},\n   {\"Bookmark this link\", 0, Menu_add_bookmark_cb,0,0,0,0,0,0},\n   {\"Copy link location\", 0, Menu_copy_urlstr_cb,0,FL_MENU_DIVIDER,0,0,0,0},\n   {\"Save link as...\", 0, Menu_save_link_cb,0,0,0,0,0,0},\n   {0,0,0,0,0,0,0,0,0}\n};\n\nstatic void Menu_set_link_menu_user_data(void *user_data)\n{\n   int i;\n\n   for (i = 0; link_menu[i].label(); i++)\n      link_menu[i].user_data(user_data);\n}\n\n/*\n * Link popup menu (construction & popup)\n */\nvoid a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url)\n{\n   static Menu_popup_data_t link_data = {\"Link menu\", NULL, link_menu};\n\n   popup_x = Fl::event_x();\n   popup_y = Fl::event_y();\n   popup_bw = bw;\n   a_Url_free(popup_url);\n   popup_url = a_Url_dup(url);\n\n   Menu_set_link_menu_user_data(popup_url);\n\n   a_Timeout_add(0.0, Menu_popup_cb, (void*)&link_data);\n}\n\n/*\n * Image popup menu (construction & popup)\n */\nvoid a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url,\n                        bool_t loaded_img, DilloUrl *page_url,\n                        DilloUrl *link_url)\n{\n   static DilloUrl *popup_page_url = NULL;\n   static DilloUrl *popup_link_url = NULL;\n   static Fl_Menu_Item pm[] = {\n      {\"Isolate image\", 0, Menu_open_url_cb,0,0,0,0,0,0},\n      {\"Open image in new tab\", 0, Menu_open_url_nt_cb,0,0,0,0,0,0},\n      {\"Open image in new window\", 0, Menu_open_url_nw_cb, 0, FL_MENU_DIVIDER,\n       0,0,0,0},\n      {\"Load image\", 0, Menu_load_images_cb,0,0,0,0,0,0},\n      {\"Bookmark this image\", 0, Menu_add_bookmark_cb,0,0,0,0,0,0},\n      {\"Copy image location\", 0,Menu_copy_urlstr_cb,0,FL_MENU_DIVIDER,0,0,0,0},\n      {\"Save image as...\", 0, Menu_save_link_cb, 0, FL_MENU_DIVIDER,0,0,0,0},\n      {\"Link menu\", 0, Menu_nop_cb, link_menu, FL_SUBMENU_POINTER,0,0,0,0},\n      {0,0,0,0,0,0,0,0,0}\n   };\n   static Menu_popup_data_t image_data = {\"Image menu\", NULL, pm};\n\n   popup_x = Fl::event_x();\n   popup_y = Fl::event_y();\n   popup_bw = bw;\n   a_Url_free(popup_url);\n   popup_url = a_Url_dup(url);\n   a_Url_free(popup_page_url);\n   popup_page_url = a_Url_dup(page_url);\n   a_Url_free(popup_link_url);\n   popup_link_url = a_Url_dup(link_url);\n\n\n   pm[0].user_data(popup_url);\n   pm[1].user_data(popup_url);\n   pm[2].user_data(popup_url);\n\n   if (loaded_img) {\n      pm[3].deactivate();\n   } else {\n      pm[3].activate();\n      pm[3].user_data(popup_page_url);\n   }\n\n   pm[4].user_data(popup_url);\n   pm[5].user_data(popup_url);\n   pm[6].user_data(popup_url);\n\n   if (link_url) {\n      pm[7].activate();\n      Menu_set_link_menu_user_data(popup_link_url);\n   } else {\n      pm[7].deactivate();\n   }\n\n   a_Timeout_add(0.0, Menu_popup_cb, (void*)&image_data);\n}\n\n/*\n * Form popup menu (construction & popup)\n */\nvoid a_Menu_form_popup(BrowserWindow *bw, const DilloUrl *page_url,\n                       void *formptr, bool_t hidvis)\n{\n   static bool hiddens_visible;\n   static Fl_Menu_Item pm[] = {\n      {\"Submit form\", 0, Menu_form_submit_cb,0,0,0,0,0,0},\n      {\"Reset form\", 0, Menu_form_reset_cb,0,0,0,0,0,0},\n      {0, 0, Menu_form_hiddens_cb, &hiddens_visible, 0,0,0,0,0},\n      {0,0,0,0,0,0,0,0,0}\n   };\n   static Menu_popup_data_t form_data = {\"Form menu\", NULL, pm};\n\n   popup_x = Fl::event_x();\n   popup_y = Fl::event_y();\n   popup_bw = bw;\n   a_Url_free(popup_url);\n   popup_url = a_Url_dup(page_url);\n   popup_form = formptr;\n\n   hiddens_visible = hidvis;\n   pm[2].label(hiddens_visible ? \"Hide hiddens\": \"Show hiddens\");\n\n   a_Timeout_add(0.0, Menu_popup_cb, (void*)&form_data);\n}\n\n/*\n * File popup menu (construction & popup)\n */\nvoid a_Menu_file_popup(BrowserWindow *bw, void *v_wid)\n{\n   Fl_Widget *wid = (Fl_Widget*)v_wid;\n\n   static Fl_Menu_Item pm[] = {\n      {\"New tab\", Keys::getShortcut(KEYS_NEW_TAB), filemenu_cb,\n       (void*)\"nt\",0,0,0,0,0},\n      {\"New window\", Keys::getShortcut(KEYS_NEW_WINDOW), filemenu_cb,\n       (void*)\"nw\", FL_MENU_DIVIDER,0,0,0,0},\n      {\"Open file...\", Keys::getShortcut(KEYS_OPEN), filemenu_cb,\n       (void*)\"of\",0,0,0,0,0},\n      {\"Open URL...\", Keys::getShortcut(KEYS_GOTO), filemenu_cb,\n       (void*)\"ou\",0,0,0,0,0},\n      {\"Close\", Keys::getShortcut(KEYS_CLOSE_TAB), filemenu_cb,\n       (void*)\"cw\", FL_MENU_DIVIDER,0,0,0,0},\n      {\"Exit\", Keys::getShortcut(KEYS_CLOSE_ALL), filemenu_cb,\n       (void*)\"ed\",0,0,0,0,0},\n      {0,0,0,0,0,0,0,0,0}\n   };\n\n   popup_bw = bw;\n   popup_x = wid->x();\n   popup_y = wid->y() + wid->h();\n   a_Url_free(popup_url);\n   popup_url = NULL;\n\n   //pm->label(wid->visible() ? NULL : \"File\");\n   a_Timeout_add(0.0, Menu_simple_popup_cb, (void*)pm);\n}\n\n/*\n * Bugmeter popup menu (construction & popup)\n */\nvoid a_Menu_bugmeter_popup(BrowserWindow *bw, const DilloUrl *url)\n{\n   static Fl_Menu_Item pm[] = {\n      {\"Validate URL with W3C\", 0, Menu_bugmeter_validate_w3c_cb,0,0,0,0,0,0},\n      {\"Validate URL with WDG\", 0, Menu_bugmeter_validate_wdg_cb, 0,\n       FL_MENU_DIVIDER,0,0,0,0},\n      {\"About bug meter\", 0, Menu_bugmeter_about_cb,0,0,0,0,0,0},\n      {0,0,0,0,0,0,0,0,0}\n   };\n\n   popup_x = Fl::event_x();\n   popup_y = Fl::event_y();\n   popup_bw = bw;\n   a_Url_free(popup_url);\n   popup_url = a_Url_dup(url);\n\n   a_Timeout_add(0.0, Menu_simple_popup_cb, (void*)pm);\n}\n\n/*\n * Navigation History popup menu (construction & popup)\n *\n * direction: {backward = -1, forward = 1}\n */\nvoid a_Menu_history_popup(BrowserWindow *bw, int x, int y, int direction)\n{\n   static Fl_Menu_Item *pm = 0;\n   int i, n;\n\n   popup_bw = bw;\n   popup_x = x;\n   popup_y = y;\n   history_direction = direction;\n\n   // TODO: hook popdown event with delete or similar.\n   if (pm)\n      delete [] pm;\n   if (history_list)\n      dFree(history_list);\n\n   // Get a list of URLs for this popup\n   history_list = a_UIcmd_get_history(bw, direction);\n\n   for (n = 0; history_list[n] != -1; n++)\n      ;\n\n   pm = new Fl_Menu_Item[n + 1];\n   memset(pm, '\\0', sizeof(Fl_Menu_Item[n + 1]));\n\n   for (i = 0; i < n; i++) {\n      pm[i].label(FL_NORMAL_LABEL, a_History_get_title(history_list[i], 1));\n      pm[i].callback(Menu_history_cb, INT2VOIDP(i+1));\n   }\n   a_Timeout_add(0.0, Menu_simple_popup_cb, (void*)pm);\n}\n\n/*\n * Toggle use of cookies\n */\nstatic void Menu_use_cookies_cb(Fl_Widget *wid, void*)\n{\n   Fl_Menu_Item *item = (Fl_Menu_Item*) wid;\n\n   item->flags ^= FL_MENU_VALUE;\n   prefs.use_cookies = item->flags & FL_MENU_VALUE ? 1 : 0;\n   a_UIcmd_repush(popup_bw);\n}\n\n/*\n * Toggle use of remote stylesheets\n */\nstatic void Menu_remote_css_cb(Fl_Widget *wid, void*)\n{\n   Fl_Menu_Item *item = (Fl_Menu_Item*) wid;\n\n   item->flags ^= FL_MENU_VALUE;\n   prefs.load_stylesheets = item->flags & FL_MENU_VALUE ? 1 : 0;\n   a_UIcmd_repush(popup_bw);\n}\n\n/*\n * Toggle use of embedded CSS style\n */\nstatic void Menu_embedded_css_cb(Fl_Widget *wid, void*)\n{\n   Fl_Menu_Item *item = (Fl_Menu_Item*) wid;\n\n   item->flags ^= FL_MENU_VALUE;\n   prefs.parse_embedded_css = item->flags & FL_MENU_VALUE ? 1 : 0;\n   a_UIcmd_repush(popup_bw);\n}\n\n/*\n * Toggle use of reader mode CSS style\n */\nstatic void Menu_reader_mode_css_cb(Fl_Widget *wid, void*)\n{\n   Fl_Menu_Item *item = (Fl_Menu_Item*) wid;\n\n   item->flags ^= FL_MENU_VALUE;\n   prefs.load_reader_mode_css = item->flags & FL_MENU_VALUE ? 1 : 0;\n   a_UIcmd_repush(popup_bw);\n}\n\nstatic void Menu_panel_change_cb(Fl_Widget*, void *user_data)\n{\n   UI *ui = (UI*)popup_bw->ui;\n\n   if (VOIDP2INT(user_data) == 10) /* small icons */\n      ui->change_panel(ui->get_panelsize(), !ui->get_smallicons());\n   else\n      ui->change_panel(VOIDP2INT(user_data), ui->get_smallicons());\n}\n\n/*\n * Toggle loading of images -- and load them if enabling.\n */\nstatic void Menu_imgload_toggle_cb(Fl_Widget *wid, void*)\n{\n   Fl_Menu_Item *item = (Fl_Menu_Item*) wid;\n\n   item->flags ^= FL_MENU_VALUE;\n\n   if ((prefs.load_images = item->flags & FL_MENU_VALUE ? 1 : 0)) {\n      void *doc = a_Bw_get_current_doc(popup_bw);\n\n      if (doc) {\n         DilloUrl *pattern = NULL;\n         a_Html_load_images(doc, pattern);\n      }\n   }\n}\n\n/*\n * Toggle loading of background images.\n */\nstatic void Menu_bgimg_load_toggle_cb(Fl_Widget *wid, void*)\n{\n   Fl_Menu_Item *item = (Fl_Menu_Item*) wid;\n\n   item->flags ^= FL_MENU_VALUE;\n   prefs.load_background_images = item->flags & FL_MENU_VALUE ? 1 : 0;\n   a_UIcmd_repush(popup_bw);\n}\n\n/*\n * Tools popup menu (construction & popup)\n */\nvoid a_Menu_tools_popup(BrowserWindow *bw, int x, int y)\n{\n   const Fl_Menu_Item *item;\n   UI *ui = (UI*)bw->ui;\n\n   static Fl_Menu_Item pm[] = {\n      {\"Use cookies\", 0, Menu_use_cookies_cb, 0,\n       FL_MENU_TOGGLE|FL_MENU_DIVIDER,0,0,0,0},\n      {\"Use remote CSS\", 0, Menu_remote_css_cb, 0, FL_MENU_TOGGLE,0,0,0,0},\n      {\"Use embedded CSS\", 0, Menu_embedded_css_cb, 0, FL_MENU_TOGGLE,0,0,0,0},\n      {\"Use reader mode CSS\", 0, Menu_reader_mode_css_cb, 0,\n       FL_MENU_TOGGLE|FL_MENU_DIVIDER,0,0,0,0},\n      {\"Load images\", 0, Menu_imgload_toggle_cb, 0,\n       FL_MENU_TOGGLE,0,0,0,0},\n      {\"Load background images\", 0, Menu_bgimg_load_toggle_cb, 0,\n       FL_MENU_TOGGLE|FL_MENU_DIVIDER,0,0,0,0},\n      {\"Panel size\", 0, Menu_nop_cb, (void*)\"Submenu1\", FL_SUBMENU,0,0,0,0},\n         {\"tiny\",  0,Menu_panel_change_cb,(void*)0,FL_MENU_RADIO,0,0,0,0},\n         {\"small\", 0,Menu_panel_change_cb,(void*)1,FL_MENU_RADIO,0,0,0,0},\n         {\"medium\",0,Menu_panel_change_cb,(void*)2,\n           FL_MENU_RADIO|FL_MENU_DIVIDER,0,0,0,0},\n         {\"small icons\", 0,Menu_panel_change_cb,(void*)10,\n           FL_MENU_TOGGLE,0,0,0,0},\n         {0,0,0,0,0,0,0,0,0},\n      {0,0,0,0,0,0,0,0,0}\n   };\n\n   popup_bw = bw;\n   int cur_panelsize = ui->get_panelsize();\n   int cur_smallicons = ui->get_smallicons();\n\n   if (prefs.use_cookies)\n      pm[0].set();\n   if (prefs.load_stylesheets)\n      pm[1].set();\n   if (prefs.parse_embedded_css)\n      pm[2].set();\n   if (prefs.load_reader_mode_css)\n      pm[3].set();\n   if (prefs.load_images)\n      pm[4].set();\n   if (prefs.load_background_images)\n      pm[5].set();\n   pm[7+cur_panelsize].setonly();\n   cur_smallicons ? pm[10].set() : pm[10].clear();\n\n   item = pm->popup(x, y);\n   if (item) {\n      ((Fl_Widget *)item)->do_callback();\n   }\n}\n\n"
  },
  {
    "path": "src/menu.hh",
    "content": "#ifndef __MENU_HH__\n#define __MENU_HH__\n\n#include \"bw.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nvoid a_Menu_page_popup(BrowserWindow *bw, const DilloUrl *url,\n                       bool_t has_bugs, void *v_cssUrls);\nvoid a_Menu_link_popup(BrowserWindow *bw, const DilloUrl *url);\nvoid a_Menu_image_popup(BrowserWindow *bw, const DilloUrl *url,\n                        bool_t loaded_img, DilloUrl *page_url,\n                        DilloUrl *link_url);\nvoid a_Menu_form_popup(BrowserWindow *bw, const DilloUrl *page_url,\n                       void *vform, bool_t showing_hiddens);\nvoid a_Menu_file_popup(BrowserWindow *bw, void *v_wid);\nvoid a_Menu_bugmeter_popup(BrowserWindow *bw, const DilloUrl *url);\nvoid a_Menu_history_popup(BrowserWindow *bw, int x, int y, int direction);\nvoid a_Menu_tools_popup(BrowserWindow *bw, int x, int y);\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* MENU_HH */\n\n"
  },
  {
    "path": "src/misc.c",
    "content": "/*\n * File: misc.c\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>,\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n#include <assert.h>\n\n#include \"utf8.hh\"\n#include \"msg.h\"\n#include \"misc.h\"\n\n/*\n * Escape characters as %XX sequences.\n * Return value: New string.\n */\nchar *a_Misc_escape_chars(const char *str, const char *esc_set)\n{\n   static const char *const hex = \"0123456789ABCDEF\";\n   char *p = NULL;\n   Dstr *dstr;\n   int i;\n\n   dstr = dStr_sized_new(64);\n   for (i = 0; str[i]; ++i) {\n      if (str[i] <= 0x1F || str[i] == 0x7F || strchr(esc_set, str[i])) {\n         dStr_append_c(dstr, '%');\n         dStr_append_c(dstr, hex[(str[i] >> 4) & 15]);\n         dStr_append_c(dstr, hex[str[i] & 15]);\n      } else {\n         dStr_append_c(dstr, str[i]);\n      }\n   }\n   p = dstr->str;\n   dStr_free(dstr, FALSE);\n\n   return p;\n}\n\n#define TAB_SIZE 8\n/*\n * Takes a string and converts any tabs to spaces.\n */\nint\na_Misc_expand_tabs(char **start, char *end, char *buf, int buflen)\n{\n   int j, pos = 0, written = 0, old_pos, char_len;\n   uint_t code;\n   static const int combining_char_space = 32;\n\n   while (*start < end && written < buflen - TAB_SIZE - combining_char_space) {\n      code = a_Utf8_decode(*start, end, &char_len);\n\n      if (code == '\\t') {\n         /* Fill with whitespaces until the next tab. */\n         old_pos = pos;\n         pos += TAB_SIZE - (pos % TAB_SIZE);\n         for (j = old_pos; j < pos; j++)\n            buf[written++] = ' ';\n      } else {\n         assert(char_len <= 4);\n         for (j = 0; j < char_len; j++)\n            buf[written++] = (*start)[j];\n         pos++;\n      }\n\n      *start += char_len;\n   }\n\n   /* If following chars are combining chars (e.g. accents) add them to the\n    * buffer. We have reserved combining_char_space bytes for this.\n    * If there should be more combining chars, we split nevertheless.\n    */\n   while (*start < end && written < buflen - 4) {\n      code = a_Utf8_decode(*start, end, &char_len);\n\n      if (! a_Utf8_combining_char(code))\n         break;\n\n      assert(char_len <= 4);\n      for (j = 0; j < char_len; j++)\n         buf[written++] = (*start)[j];\n\n      *start += char_len;\n   }\n\n   return written;\n}\n\n/* TODO: could use dStr ADT! */\ntypedef struct {\n   const char *str;\n   int len;\n} ContentType_t;\n\nstatic const ContentType_t MimeTypes[] = {\n   { \"application/octet-stream\", 24 },\n   { \"application/xhtml+xml\", 21 },\n   { \"text/html\", 9 },\n   { \"text/plain\", 10 },\n   { \"image/gif\", 9 },\n   { \"image/png\", 9 },\n   { \"image/jpeg\", 10 },\n   { NULL, 0 }\n};\n\ntypedef enum {\n   DT_OCTET_STREAM = 0,\n   DT_PLACEHOLDER,\n   DT_TEXT_HTML,\n   DT_TEXT_PLAIN,\n   DT_IMAGE_GIF,\n   DT_IMAGE_PNG,\n   DT_IMAGE_JPG,\n} DetectedContentType;\n\n/*\n * Detects 'Content-Type' from a data stream sample.\n *\n * It uses the magic(5) logic from file(1). Currently, it\n * only checks the few mime types that Dillo supports.\n *\n * 'Data' is a pointer to the first bytes of the raw data.\n *\n * Return value: (0 on success, 1 on doubt, 2 on lack of data).\n */\nint a_Misc_get_content_type_from_data(void *Data, size_t Size, const char **PT)\n{\n   size_t i, non_ascci, non_ascci_text, bin_chars;\n   char *p = Data;\n   int st = 1;      /* default to \"doubt' */\n   DetectedContentType Type = DT_OCTET_STREAM; /* default to binary */\n\n   /* HTML try */\n   for (i = 0; i < Size && dIsspace(p[i]); ++i);\n   if ((Size - i >= 5  && !dStrnAsciiCasecmp(p+i, \"<html\", 5)) ||\n       (Size - i >= 5  && !dStrnAsciiCasecmp(p+i, \"<head\", 5)) ||\n       (Size - i >= 6  && !dStrnAsciiCasecmp(p+i, \"<title\", 6)) ||\n       (Size - i >= 14 && !dStrnAsciiCasecmp(p+i, \"<!doctype html\", 14)) ||\n       /* this line is workaround for FTP through the Squid proxy */\n       (Size - i >= 17 && !dStrnAsciiCasecmp(p+i, \"<!-- HTML listing\", 17))) {\n\n      Type = DT_TEXT_HTML;\n      st = 0;\n   /* Images */\n   } else if (Size >= 4 && !strncmp(p, \"GIF8\", 4)) {\n      Type = DT_IMAGE_GIF;\n      st = 0;\n   } else if (Size >= 4 && !strncmp(p, \"\\x89PNG\", 4)) {\n      Type = DT_IMAGE_PNG;\n      st = 0;\n   } else if (Size >= 2 && !strncmp(p, \"\\xff\\xd8\", 2)) {\n      /* JPEG has the first 2 bytes set to 0xffd8 in BigEndian - looking\n       * at the character representation should be machine independent. */\n      Type = DT_IMAGE_JPG;\n      st = 0;\n\n   /* Text */\n   } else {\n      /* Heuristic for \"text/plain\"\n       * {ASCII, LATIN1, UTF8, KOI8-R, CP-1251}\n       * All in the above set regard [00-31] as control characters.\n       * LATIN1: [7F-9F] unused\n       * CP-1251 {7F,98} unused (two characters).\n       *\n       * We'll use [0-31] as indicators of non-text content.\n       * Better heuristics are welcomed! :-) */\n\n      non_ascci = non_ascci_text = bin_chars = 0;\n      Size = MIN (Size, 256);\n      for (i = 0; i < Size; i++) {\n         int ch = (uchar_t) p[i];\n         if (ch < 32 && !dIsspace(ch))\n            ++bin_chars;\n         if (ch > 126)\n            ++non_ascci;\n         if (ch > 190)\n            ++non_ascci_text;\n      }\n      if (bin_chars == 0 && (non_ascci - non_ascci_text) <= Size/10) {\n         /* Let's say text: if \"rare\" chars are <= 10% */\n         Type = DT_TEXT_PLAIN;\n      } else if (Size > 0) {\n         /* a special check for UTF-8 */\n         Size = a_Utf8_end_of_char(p, Size - 1) + 1;\n         if (a_Utf8_test(p, Size) > 0)\n            Type = DT_TEXT_PLAIN;\n      }\n      if (Size >= 256)\n         st = 0;\n   }\n\n   *PT = MimeTypes[Type].str;\n   return st;\n}\n\n/*\n * Parse Content-Type string, e.g., \"text/html; charset=utf-8\".\n * Content-Type is defined in RFC 2045 section 5.1.\n */\nvoid a_Misc_parse_content_type(const char *type, char **major, char **minor,\n                               char **charset)\n{\n   static const char tspecials_space[] = \"()<>@,;:\\\\\\\"/[]?= \";\n   const char *str, *s;\n\n   if (major)\n      *major = NULL;\n   if (minor)\n      *minor = NULL;\n   if (charset)\n      *charset = NULL;\n   if (!(str = type))\n      return;\n\n   for (s = str; *s && isascii((uchar_t)*s) && !iscntrl((uchar_t)*s) &&\n        !strchr(tspecials_space, *s); s++) ;\n   if (major)\n      *major = dStrndup(str, s - str);\n\n   if (*s == '/') {\n      for (str = ++s; *s && isascii((uchar_t)*s) && !iscntrl((uchar_t)*s) &&\n           !strchr(tspecials_space, *s); s++) ;\n      if (minor)\n         *minor = dStrndup(str, s - str);\n   }\n   if (charset && *s &&\n       (dStrnAsciiCasecmp(type, \"text/\", 5) == 0 ||\n        dStrnAsciiCasecmp(type, \"application/xhtml+xml\", 21) == 0)) {\n      /* \"charset\" parameter defined for text media type in RFC 2046,\n       * application/xhtml+xml in RFC 3236.\n       *\n       * Note that RFC 3023 lists some main xml media types and provides\n       * the convention of using the \"+xml\" minor type suffix for other\n       * xml types, so it would be reasonable to check for that suffix if\n       * we have need to care about various xml types someday.\n       */\n      const char terminators[] = \" ;\\t\";\n      const char key[] = \"charset\";\n\n      if ((s = dStriAsciiStr(str, key)) &&\n          (s == str || strchr(terminators, s[-1]))) {\n         s += sizeof(key) - 1;\n         for ( ; *s == ' ' || *s == '\\t'; ++s);\n         if (*s == '=') {\n            size_t len;\n            for (++s; *s == ' ' || *s == '\\t'; ++s);\n            if ((len = strcspn(s, terminators))) {\n               if (*s == '\"' && s[len-1] == '\"' && len > 1) {\n                 /* quoted string */\n                 s++;\n                 len -= 2;\n               }\n               *charset = dStrndup(s, len);\n            }\n         }\n      }\n   }\n}\n\n/*\n * Compare two Content-Type strings.\n * Return 0 if they are equivalent, and 1 otherwise.\n */\nint a_Misc_content_type_cmp(const char *ct1, const char *ct2)\n{\n   char *major1, *major2, *minor1, *minor2, *charset1, *charset2;\n   int ret;\n\n   if ((!ct1 || !*ct1) && (!ct2 || !*ct2))\n      return 0;\n   if ((!ct1 || !*ct1) || (!ct2 || !*ct2))\n      return 1;\n\n   a_Misc_parse_content_type(ct1, &major1, &minor1, &charset1);\n   a_Misc_parse_content_type(ct2, &major2, &minor2, &charset2);\n\n   if (major1 && major2 && !dStrAsciiCasecmp(major1, major2) &&\n       minor1 && minor2 && !dStrAsciiCasecmp(minor1, minor2) &&\n       ((!charset1 && !charset2) ||\n        (charset1 && charset2 && !dStrAsciiCasecmp(charset1, charset2)) ||\n        (!charset1 && charset2 && !dStrAsciiCasecmp(charset2, \"UTF-8\")) ||\n        (charset1 && !charset2 && !dStrAsciiCasecmp(charset1, \"UTF-8\")))) {\n      ret = 0;\n   } else {\n      ret = 1;\n   }\n   dFree(major1); dFree(major2);\n   dFree(minor1); dFree(minor2);\n   dFree(charset1); dFree(charset2);\n\n   return ret;\n}\n\n/*\n * Check the server-supplied 'Content-Type' against our detected type.\n * (some servers seem to default to \"text/plain\").\n *\n * Return value:\n *  0,  if they match\n *  -1, if a mismatch is detected\n *\n * There are many MIME types Dillo doesn't know, they're handled\n * as \"application/octet-stream\" (as the SPEC says).\n *\n * A mismatch happens when receiving a binary stream as\n * \"text/plain\" or \"text/html\", or an image that's not an image of its kind.\n *\n * Note: this is a basic security procedure.\n *\n */\nint a_Misc_content_type_check(const char *EntryType, const char *DetectedType)\n{\n   int i;\n   int st = -1;\n\n   _MSG(\"Type check:  [Srv: %s  Det: %s]\\n\", EntryType, DetectedType);\n\n   if (!EntryType)\n      return 0; /* there's no mismatch without server type */\n\n   for (i = 1; MimeTypes[i].str; ++i)\n      if (dStrnAsciiCasecmp(EntryType, MimeTypes[i].str, MimeTypes[i].len) ==0)\n         break;\n\n   if (!MimeTypes[i].str) {\n      /* type not found, no mismatch */\n      st = 0;\n   } else if (dStrnAsciiCasecmp(EntryType, \"image/\", 6) == 0 &&\n             !dStrnAsciiCasecmp(DetectedType, MimeTypes[i].str,\n                                MimeTypes[i].len)){\n      /* An image, and there's an exact match */\n      st = 0;\n   } else if (dStrnAsciiCasecmp(EntryType, \"text/\", 5) ||\n              dStrnAsciiCasecmp(DetectedType, \"application/\", 12)) {\n      /* Not an application sent as text */\n      st = 0;\n   } else if (dStrnAsciiCasecmp(EntryType, \"application/xhtml+xml\", 21) &&\n              dStrnAsciiCasecmp(DetectedType, \"text/html\", 9)) {\n      /* XML version of HTML */\n      st = 0;\n   }\n   _MSG(\"Type check: %s\\n\", st == 0 ? \"MATCH\" : \"MISMATCH\");\n\n   return st;\n}\n\n/*\n * Parse a geometry string.\n */\nint a_Misc_parse_geometry(char *str, int *x, int *y, int *w, int *h)\n{\n   char *p, *t1, *t2;\n   int n1, n2;\n   int ret = 0;\n\n   if ((p = strchr(str, 'x')) || (p = strchr(str, 'X'))) {\n      n1 = strtol(str, &t1, 10);\n      n2 = strtol(++p, &t2, 10);\n      if (t1 != str && t2 != p) {\n         *w = n1;\n         *h = n2;\n         ret = 1;\n         /* parse x,y now */\n         p = t2;\n         n1 = strtol(p, &t1, 10);\n         n2 = strtol(t1, &t2, 10);\n         if (t1 != p && t2 != t1) {\n            *x = n1;\n            *y = n2;\n         }\n      }\n   }\n   _MSG(\"geom: w,h,x,y = (%d,%d,%d,%d)\\n\", *w, *h, *x, *y);\n   return ret;\n}\n\n/*\n * Parse dillorc's search_url string (\"[<label> ]<url>\")\n * Return value: -1 on error, 0 on success (and label and urlstr pointers)\n */\nint a_Misc_parse_search_url(char *source, char **label, char **urlstr)\n{\n   static char buf[32];\n   char *p, *q;\n   int ret = -1;\n\n   if ((p = strrchr(source, ' '))) {\n      /* label and url pair */\n      strncpy(buf,source,MIN(p-source,31));\n      buf[MIN(p-source,31)] = 0;\n      source = p+1;\n      if ((p = strchr(source, '/')) && p[1] && (q = strchr(p+2,'/'))) {\n         *urlstr = source;\n         ret = 0;\n      }\n   } else {\n      /* url only, make a custom label */\n      if ((p = strchr(source, '/')) && p[1] && (q = strchr(p+2,'/'))) {\n         strncpy(buf,p+2,MIN(q-p-2,31));\n         buf[MIN(q-p-2,31)] = 0;\n         *urlstr = source;\n         ret = 0;\n      }\n   }\n   *label = buf;\n   if (ret == -1)\n      MSG(\"Invalid search_url: \\\"%s\\\"\\n\", source);\n   return ret;\n}\n\n/*\n * Encodes string using base64 encoding.\n * Return value: new string or NULL if input string is empty.\n */\nchar *a_Misc_encode_base64(const char *in)\n{\n   static const char *const base64_hex = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n                                         \"abcdefghijklmnopqrstuvwxyz\"\n                                         \"0123456789+/\";\n   char *out = NULL;\n   int len, i = 0;\n\n   if (in == NULL) return NULL;\n   len = strlen(in);\n\n   out = (char *)dMalloc((len + 2) / 3 * 4 + 1);\n\n   for (; len >= 3; len -= 3) {\n      out[i++] = base64_hex[in[0] >> 2];\n      out[i++] = base64_hex[((in[0]<<4) & 0x30) | (in[1]>>4)];\n      out[i++] = base64_hex[((in[1]<<2) & 0x3c) | (in[2]>>6)];\n      out[i++] = base64_hex[in[2] & 0x3f];\n      in += 3;\n   }\n\n   if (len > 0) {\n      unsigned char fragment;\n      out[i++] = base64_hex[in[0] >> 2];\n      fragment = (in[0] << 4) & 0x30;\n      if (len > 1) fragment |= in[1] >> 4;\n      out[i++] = base64_hex[fragment];\n      out[i++] = (len < 2) ? '=' : base64_hex[(in[1] << 2) & 0x3c];\n      out[i++] = '=';\n   }\n   out[i] = '\\0';\n   return out;\n}\n\n/*\n * Load a local file into a dStr.\n * Return value: dStr on success, NULL on error.\n * TODO: a filesize threshold may be implemented.\n */\nDstr *a_Misc_file2dstr(const char *filename)\n{\n   FILE *F_in;\n   int n;\n   char buf[4096];\n   Dstr *dstr = NULL;\n\n   if ((F_in = fopen(filename, \"r\"))) {\n      dstr = dStr_sized_new(4096);\n      while ((n = fread (buf, 1, 4096, F_in)) > 0) {\n         dStr_append_l(dstr, buf, n);\n      }\n      fclose(F_in);\n   }\n   return dstr;\n}\n"
  },
  {
    "path": "src/misc.h",
    "content": "#ifndef __DILLO_MISC_H__\n#define __DILLO_MISC_H__\n\n#include <stddef.h>     /* for size_t */\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\nchar *a_Misc_escape_chars(const char *str, const char *esc_set);\nint a_Misc_expand_tabs(char **start, char *end, char *buf, int buflen);\nint a_Misc_get_content_type_from_data(void *Data, size_t Size,const char **PT);\nint a_Misc_content_type_check(const char *EntryType, const char *DetectedType);\nvoid a_Misc_parse_content_type(const char *str, char **major, char **minor,\n                               char **charset);\nint a_Misc_content_type_cmp(const char* ct1, const char *ct2);\nint a_Misc_parse_geometry(char *geom, int *x, int *y, int *w, int *h);\nint a_Misc_parse_search_url(char *source, char **label, char **urlstr);\nchar *a_Misc_encode_base64(const char *in);\nDstr *a_Misc_file2dstr(const char *filename);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __DILLO_MISC_H__ */\n\n"
  },
  {
    "path": "src/msg.h",
    "content": "#ifndef __MSG_H__\n#define __MSG_H__\n\n#include <stdio.h>\n#include \"prefs.h\"\n\n/*\n * You can disable any MSG* macro by adding the '_' prefix.\n */\n#define _MSG(...)\n#define _MSG_WARN(...)\n#define _MSG_HTTP(...)\n\n#define MSG_INNARDS(prefix, ...)                   \\\n   D_STMT_START {                                  \\\n      if (prefs.show_msg){                         \\\n         printf(prefix __VA_ARGS__);               \\\n         fflush (stdout);                          \\\n      }                                            \\\n   } D_STMT_END\n\n#define MSG(...) MSG_INNARDS(\"\", __VA_ARGS__)\n#define MSG_WARN(...) MSG_INNARDS(\"** WARNING **: \", __VA_ARGS__)\n#define MSG_ERR(...) MSG_INNARDS(\"** ERROR **: \", __VA_ARGS__)\n#define MSG_HTTP(...) MSG_INNARDS(\"HTTP warning: \", __VA_ARGS__)\n\n#endif /* __MSG_H__ */\n"
  },
  {
    "path": "src/nav.c",
    "content": "/*\n * File: nav.c\n *\n * Copyright (C) 2000-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/* Support for a navigation stack */\n\n#include <stdio.h>\n#include <sys/stat.h>\n#include \"msg.h\"\n#include \"nav.h\"\n#include \"history.h\"\n#include \"web.hh\"\n#include \"uicmd.hh\"\n#include \"dialog.hh\"\n#include \"prefs.h\"\n#include \"capi.h\"\n#include \"timeout.hh\"\n\n/*\n * For back and forward navigation, each bw keeps an url index,\n * and its scroll position.\n */\ntypedef struct {\n   int url_idx;\n   int posx, posy;\n} nav_stack_item;\n\n\n\n/*\n * Free memory used by this module\n * TODO: this may be removed or called by a_Bw_free().\n  *      Currently is not called from anywhere.\n */\nvoid a_Nav_free(BrowserWindow *bw)\n{\n   a_Nav_cancel_expect(bw);\n   dFree(bw->nav_stack);\n}\n\n\n/* Navigation stack methods ------------------------------------------------ */\n\n/*\n * Return current nav_stack pointer [0 based; -1 = empty]\n */\nint a_Nav_stack_ptr(BrowserWindow *bw)\n{\n   return bw->nav_stack_ptr;\n}\n\n/*\n * Return the url index of i-th element in the stack. [-1 = Error]\n */\nint a_Nav_get_uidx(BrowserWindow *bw, int i)\n{\n   nav_stack_item *nsi = dList_nth_data (bw->nav_stack, i);\n   return (nsi) ? nsi->url_idx : -1;\n}\n\n/*\n * Return the url index of the top element in the stack.\n */\nint a_Nav_get_top_uidx(BrowserWindow *bw)\n{\n   nav_stack_item *nsi;\n\n   nsi = dList_nth_data (bw->nav_stack, a_Nav_stack_ptr(bw));\n   return (nsi) ? nsi->url_idx : -1;\n}\n\n/*\n * Move the nav_stack pointer\n */\nstatic void Nav_stack_move_ptr(BrowserWindow *bw, int offset)\n{\n   int nptr;\n\n   dReturn_if_fail (bw != NULL);\n   if (offset != 0) {\n      nptr = bw->nav_stack_ptr + offset;\n      dReturn_if_fail (nptr >= 0 && nptr < a_Nav_stack_size(bw));\n      bw->nav_stack_ptr = nptr;\n   }\n}\n\n/*\n * Return size of nav_stack [1 based]\n */\nint a_Nav_stack_size(BrowserWindow *bw)\n{\n   return dList_length(bw->nav_stack);\n}\n\n/*\n * Truncate the navigation stack including 'pos' and upwards.\n */\nstatic void Nav_stack_truncate(BrowserWindow *bw, int pos)\n{\n   void *data;\n\n   dReturn_if_fail (bw != NULL && pos >= 0);\n\n   while (pos < dList_length(bw->nav_stack)) {\n      data = dList_nth_data(bw->nav_stack, pos);\n      dList_remove_fast (bw->nav_stack, data);\n      dFree(data);\n   }\n}\n\n/*\n * Insert a nav_stack_item into the stack at a given position.\n */\nstatic void Nav_stack_append(BrowserWindow *bw, int url_idx)\n{\n   nav_stack_item *nsi;\n\n   dReturn_if_fail (bw != NULL);\n\n   /* Insert the new element */\n   nsi = dNew(nav_stack_item, 1);\n   nsi->url_idx = url_idx;\n   nsi->posx = 0;\n   nsi->posy = 0;\n   dList_append (bw->nav_stack, nsi);\n}\n\n/*\n * Get the scrolling position of the current page.\n */\nstatic void Nav_get_scroll_pos(BrowserWindow *bw, int *posx, int *posy)\n{\n   nav_stack_item *nsi;\n\n   if ((nsi = dList_nth_data (bw->nav_stack, a_Nav_stack_ptr(bw)))) {\n      *posx = nsi->posx;\n      *posy = nsi->posy;\n   } else {\n      *posx = *posy = 0;\n   }\n}\n\n/*\n * Save the scrolling position of the current page.\n */\nstatic void Nav_save_scroll_pos(BrowserWindow *bw, int idx, int posx, int posy)\n{\n   nav_stack_item *nsi;\n\n   if ((nsi = dList_nth_data (bw->nav_stack, idx))) {\n      nsi->posx = posx;\n      nsi->posy = posy;\n   }\n}\n\n/*\n * Remove equal adjacent URLs at the top of the stack.\n * (It may happen with redirections)\n */\nstatic void Nav_stack_clean(BrowserWindow *bw)\n{\n   int i;\n\n   dReturn_if_fail (bw != NULL);\n\n   if ((i = a_Nav_stack_size(bw)) >= 2 &&\n       NAV_UIDX(bw,i - 2) == NAV_UIDX(bw,i - 1)) {\n      void *data = dList_nth_data (bw->nav_stack, i - 1);\n      dList_remove_fast (bw->nav_stack, data);\n      dFree(data);\n      if (bw->nav_stack_ptr >= a_Nav_stack_size(bw))\n         bw->nav_stack_ptr = a_Nav_stack_size(bw) - 1;\n   }\n}\n\n\n/* General methods --------------------------------------------------------- */\n\n/*\n * Create a DilloWeb structure for 'url' and ask the cache to send it back.\n *  - Also set a few things related to the browser window.\n * This function requests the page's root-URL; images and related stuff\n * are fetched directly by the HTML module.\n */\nstatic void Nav_open_url(BrowserWindow *bw, const DilloUrl *url,\n                         const DilloUrl *requester, int offset)\n{\n   const DilloUrl *old_url;\n   bool_t MustLoad, ForceReload, IgnoreScroll;\n   int x, y, idx, ClientKey;\n   DilloWeb *Web;\n\n   MSG(\"Nav_open_url: new url='%s'\\n\", URL_STR_(url));\n\n   ForceReload = (URL_FLAGS(url) & (URL_E2EQuery + URL_ReloadFromCache)) != 0;\n   IgnoreScroll = (URL_FLAGS(url) & URL_IgnoreScroll) != 0;\n\n   /* Get the url of the current page */\n   idx = a_Nav_stack_ptr(bw);\n   old_url = a_History_get_url(NAV_UIDX(bw, idx));\n   _MSG(\"Nav_open_url:  old_url='%s' idx=%d\\n\", URL_STR(old_url), idx);\n   /* Record current scrolling position */\n   if (old_url && !IgnoreScroll) {\n      a_UIcmd_get_scroll_xy(bw, &x, &y);\n      Nav_save_scroll_pos(bw, idx, x, y);\n      _MSG(\"Nav_open_url:  saved scroll of '%s' at x=%d y=%d\\n\",\n          URL_STR(old_url), x, y);\n   }\n\n   /* Update navigation-stack-pointer (offset may be zero) */\n   Nav_stack_move_ptr(bw, offset);\n\n   /* Page must be reloaded, if old and new url (considering anchor) differ */\n   MustLoad = ForceReload || !old_url;\n   if (old_url){\n      MustLoad |= (a_Url_cmp(old_url, url) ||\n                   strcmp(URL_FRAGMENT(old_url), URL_FRAGMENT(url)));\n   }\n\n   if (MustLoad) {\n      a_Bw_stop_clients(bw, BW_Root + BW_Img);\n      a_Bw_cleanup(bw);\n\n      // a_Menu_pagemarks_new(bw);\n\n      Web = a_Web_new(bw, url, requester);\n      Web->flags |= WEB_RootUrl;\n      if ((ClientKey = a_Capi_open_url(Web, NULL, NULL)) != 0) {\n         a_Bw_add_client(bw, ClientKey, 1);\n         a_Bw_add_url(bw, url);\n      }\n   }\n}\n\n/*\n * Cancel the last expected url if present. The responsibility\n * for actually aborting the data stream remains with the caller.\n */\nvoid a_Nav_cancel_expect(BrowserWindow *bw)\n{\n   if (a_Bw_expecting(bw)) {\n      a_Bw_cancel_expect(bw);\n      a_UIcmd_set_buttons_sens(bw);\n   }\n   if (bw->meta_refresh_status > 0)\n      --bw->meta_refresh_status;\n}\n\n/*\n * Cancel the expect if 'url' matches.\n */\nvoid a_Nav_cancel_expect_if_eq(BrowserWindow *bw, const DilloUrl *url)\n{\n   if (a_Url_cmp(url, a_Bw_expected_url(bw)) == 0)\n      a_Nav_cancel_expect(bw);\n}\n\n/*\n * We have an answer! Set things accordingly.\n * This function is called for root URLs only.\n * Beware: this function is much more complex than it looks\n *         because URLs with repush pass twice through it.\n */\nvoid a_Nav_expect_done(BrowserWindow *bw)\n{\n   int m, url_idx, posx, posy, reload, repush, e2equery, goto_old_scroll=TRUE;\n   DilloUrl *url;\n   char *fragment = NULL;\n\n   dReturn_if_fail(bw != NULL);\n\n   if (a_Bw_expecting(bw)) {\n      url = a_Url_dup(a_Bw_expected_url(bw));\n      reload = (URL_FLAGS(url) & URL_ReloadPage);\n      repush = (URL_FLAGS(url) & URL_ReloadFromCache);\n      e2equery = (URL_FLAGS(url) & URL_E2EQuery);\n      fragment = a_Url_decode_hex_str(URL_FRAGMENT_(url));\n\n      /* Unset E2EQuery, ReloadPage, ReloadFromCache and IgnoreScroll\n       * before adding this url to history */\n      m = URL_E2EQuery|URL_ReloadPage|URL_ReloadFromCache|URL_IgnoreScroll;\n      a_Url_set_flags(url, URL_FLAGS(url) & ~m);\n      url_idx = a_History_add_url(url);\n      a_Url_free(url);\n\n      if (repush) {\n         MSG(\"a_Nav_expect_done: repush!\\n\");\n      } else if (reload) {\n         MSG(\"a_Nav_expect_done: reload!\\n\");\n      } else {\n         Nav_stack_truncate(bw, a_Nav_stack_ptr(bw) + 1);\n         Nav_stack_append(bw, url_idx);\n         Nav_stack_move_ptr(bw, 1);\n      }\n\n      if (fragment) {\n         goto_old_scroll = FALSE;\n         if (repush) {\n            Nav_get_scroll_pos(bw, &posx, &posy);\n            if (posx || posy)\n               goto_old_scroll = TRUE;\n         } else if (e2equery) {\n            /* Reset scroll, so repush goes to fragment in the next pass */\n            Nav_save_scroll_pos(bw, a_Nav_stack_ptr(bw), 0, 0);\n         }\n      }\n      a_Nav_cancel_expect(bw);\n   }\n\n   if (goto_old_scroll) {\n      /* Scroll to where we were in this page */\n      Nav_get_scroll_pos(bw, &posx, &posy);\n      a_UIcmd_set_scroll_xy(bw, posx, posy);\n      _MSG(\"Nav: expect_done scrolling to x=%d y=%d\\n\", posx, posy);\n   } else if (fragment) {\n      /* Scroll to fragment */\n      a_UIcmd_set_scroll_by_fragment(bw, fragment);\n   } else {\n      /* Scroll to origin */\n      a_UIcmd_set_scroll_xy(bw, 0, 0);\n   }\n\n   dFree(fragment);\n   Nav_stack_clean(bw);\n   a_UIcmd_set_buttons_sens(bw);\n   _MSG(\"Nav: a_Nav_expect_done\\n\");\n}\n\n/*\n * Make 'url' the current browsed page (upon data arrival)\n * - Set bw to expect the URL data\n * - Ask the cache to feed back the requested URL (via Nav_open_url)\n */\nvoid a_Nav_push(BrowserWindow *bw, const DilloUrl *url,\n                                   const DilloUrl *requester)\n{\n   const DilloUrl *e_url;\n   dReturn_if_fail (bw != NULL);\n\n   e_url = a_Bw_expected_url(bw);\n   if (e_url && !a_Url_cmp(e_url, url) &&\n       !strcmp(URL_FRAGMENT(e_url),URL_FRAGMENT(url))) {\n      /* we're already expecting that url (most probably a double-click) */\n      return;\n   }\n   a_Nav_cancel_expect(bw);\n   a_Bw_expect(bw, url);\n   Nav_open_url(bw, url, requester, 0);\n   a_UIcmd_set_location_text(bw, URL_STR(url));\n}\n\n/*\n * This one does a_Nav_repush's job.\n */\nstatic void Nav_repush(BrowserWindow *bw)\n{\n   DilloUrl *url;\n\n   a_Nav_cancel_expect(bw);\n   if (a_Nav_stack_size(bw)) {\n      url = a_Url_dup(a_History_get_url(NAV_TOP_UIDX(bw)));\n      /* Let's make reload be from Cache */\n      a_Url_set_flags(url, URL_FLAGS(url) | URL_ReloadFromCache);\n      a_Bw_expect(bw, url);\n      Nav_open_url(bw, url, NULL, 0);\n      a_Url_free(url);\n   }\n}\n\nstatic void Nav_repush_callback(void *data)\n{\n   _MSG(\">>>> Nav_repush_callback <<<<\\n\");\n   Nav_repush(data);\n   a_Timeout_remove();\n}\n\n/*\n * Repush current URL: not an end-to-end reload but from cache.\n * - Currently used to switch to a charset decoder given by the META element.\n * - Delayed to let dillo finish the call flow into a known state.\n *\n * There's no need to stop the parser before calling this function:\n * When the timeout activates, a_Bw_stop_clients will stop the data feed.\n */\nvoid a_Nav_repush(BrowserWindow *bw)\n{\n   dReturn_if_fail (bw != NULL);\n   MSG(\">>>> a_Nav_repush <<<<\\n\");\n   a_Timeout_add(0.0, Nav_repush_callback, (void*)bw);\n}\n\n/*\n * This one does a_Nav_redirection0's job.\n */\nstatic void Nav_redirection0_callback(void *data)\n{\n   BrowserWindow *bw = (BrowserWindow *)data;\n   const DilloUrl *referer_url = a_History_get_url(NAV_TOP_UIDX(bw));\n   _MSG(\">>>> Nav_redirection0_callback <<<<\\n\");\n\n   if (bw->meta_refresh_status == 2) {\n      Nav_stack_move_ptr(bw, -1);\n      a_Nav_push(bw, bw->meta_refresh_url, referer_url);\n   }\n   a_Url_free(bw->meta_refresh_url);\n   bw->meta_refresh_url = NULL;\n   bw->meta_refresh_status = 0;\n   a_Timeout_remove();\n}\n\n/*\n * Handle a zero-delay URL redirection given by META\n */\nvoid a_Nav_redirection0(BrowserWindow *bw, const DilloUrl *new_url)\n{\n   dReturn_if_fail (bw != NULL);\n   _MSG(\">>>> a_Nav_redirection0 <<<<\\n\");\n\n   a_Url_free(bw->meta_refresh_url);\n   bw->meta_refresh_url = a_Url_dup(new_url);\n   a_Url_set_flags(bw->meta_refresh_url,\n                   URL_FLAGS(new_url)|URL_E2EQuery|URL_IgnoreScroll);\n   bw->meta_refresh_status = 2;\n   a_Timeout_add(0.0, Nav_redirection0_callback, (void*)bw);\n}\n\n/*\n * Send the browser back to previous page\n */\nvoid a_Nav_back(BrowserWindow *bw)\n{\n   int idx = a_Nav_stack_ptr(bw);\n\n   a_Nav_cancel_expect(bw);\n   if (--idx >= 0){\n      a_UIcmd_set_msg(bw, \"\");\n      Nav_open_url(bw, a_History_get_url(NAV_UIDX(bw,idx)), NULL, -1);\n   }\n}\n\n/*\n * Send the browser to next page in the history list\n */\nvoid a_Nav_forw(BrowserWindow *bw)\n{\n   int idx = a_Nav_stack_ptr(bw);\n\n   a_Nav_cancel_expect(bw);\n   if (++idx < a_Nav_stack_size(bw)) {\n      a_UIcmd_set_msg(bw, \"\");\n      Nav_open_url(bw, a_History_get_url(NAV_UIDX(bw,idx)), NULL, +1);\n   }\n}\n\n/*\n * Redirect the browser to the HOME page!\n */\nvoid a_Nav_home(BrowserWindow *bw)\n{\n   a_Nav_push(bw, prefs.home, NULL);\n}\n\n/*\n * This one does a_Nav_reload's job!\n */\nstatic void Nav_reload_callback(void *data)\n{\n   BrowserWindow *bw = data;\n   const DilloUrl *h_url;\n   DilloUrl *r_url;\n   int choice, confirmed = 1;\n\n   a_Nav_cancel_expect(bw);\n   if (a_Nav_stack_size(bw)) {\n      h_url = a_History_get_url(NAV_TOP_UIDX(bw));\n      if (dStrAsciiCasecmp(URL_SCHEME(h_url), \"dpi\") == 0 &&\n          strncmp(URL_PATH(h_url), \"/vsource/\", 9) == 0) {\n         /* allow reload for view source dpi */\n         confirmed = 1;\n      } else if (URL_FLAGS(h_url) & URL_Post) {\n         /* Attempt to repost data, let's confirm... */\n         choice = a_Dialog_choice(\"Dillo: Repost form?\",\n                                  \"Repost form data?\",\n                                  \"No\", \"Yes\", \"Cancel\", NULL);\n         confirmed = (choice == 2);  /* \"Yes\" */\n      }\n\n      if (confirmed) {\n         r_url = a_Url_dup(h_url);\n         /* Mark URL as reload to differentiate from push */\n         a_Url_set_flags(r_url, URL_FLAGS(r_url) | URL_ReloadPage);\n         /* Let's make reload be end-to-end */\n         a_Url_set_flags(r_url, URL_FLAGS(r_url) | URL_E2EQuery);\n         /* This is an explicit reload, so clear the SpamSafe flag */\n         a_Url_set_flags(r_url, URL_FLAGS(r_url) & ~URL_SpamSafe);\n         a_Bw_expect(bw, r_url);\n         Nav_open_url(bw, r_url, NULL, 0);\n         a_Url_free(r_url);\n      }\n   }\n}\n\n/*\n * Implement the RELOAD button functionality.\n * (Currently it only reloads the page, not its images)\n * Note: the timeout lets CCC operations end before making the request.\n */\nvoid a_Nav_reload(BrowserWindow *bw)\n{\n   dReturn_if_fail (bw != NULL);\n   a_Timeout_add(0.0, Nav_reload_callback, (void*)bw);\n}\n\n/*\n * Jump to a URL in the Navigation stack.\n */\nvoid a_Nav_jump(BrowserWindow *bw, int offset, int new_bw)\n{\n   int idx = a_Nav_stack_ptr(bw) + offset;\n\n   if (new_bw) {\n      a_UIcmd_open_url_nw(bw, a_History_get_url(NAV_UIDX(bw,idx)));\n   } else {\n      a_Nav_cancel_expect(bw);\n      Nav_open_url(bw, a_History_get_url(NAV_UIDX(bw,idx)), NULL, offset);\n      a_UIcmd_set_buttons_sens(bw);\n   }\n}\n\n\n/* Specific methods -------------------------------------------------------- */\n\n/*\n * Receive data from the cache and save it to a local file\n */\nstatic void Nav_save_cb(int Op, CacheClient_t *Client)\n{\n   DilloWeb *Web = Client->Web;\n   int Bytes;\n\n   if (Op){\n      struct stat st;\n\n      fflush(Web->stream);\n      fstat(fileno(Web->stream), &st);\n      fclose(Web->stream);\n      a_UIcmd_set_msg(Web->bw, \"File saved (%d Bytes)\", st.st_size);\n   } else {\n      if ((Bytes = Client->BufSize - Web->SavedBytes) > 0) {\n         Bytes = fwrite(Client->Buf + Web->SavedBytes, 1, Bytes, Web->stream);\n         Web->SavedBytes += Bytes;\n      }\n   }\n}\n\n/*\n * Save a URL (from cache or from the net).\n */\nvoid a_Nav_save_url(BrowserWindow *bw,\n                    const DilloUrl *url, const char *filename)\n{\n   DilloWeb *Web = a_Web_new(bw, url, NULL);\n   Web->filename = dStrdup(filename);\n   Web->flags |= WEB_Download;\n   /* TODO: keep track of this client */\n   a_Capi_open_url(Web, Nav_save_cb, Web);\n}\n\n/*\n * Wrapper for a_Capi_get_buf.\n */\nint a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize)\n{\n   return a_Capi_get_buf(Url, PBuf, BufSize);\n}\n\n/*\n * Wrapper for a_Capi_unref_buf().\n */\nvoid a_Nav_unref_buf(const DilloUrl *Url)\n{\n   a_Capi_unref_buf(Url);\n}\n\n/*\n * Wrapper for a_Capi_get_content_type().\n */\nconst char *a_Nav_get_content_type(const DilloUrl *url)\n{\n   return a_Capi_get_content_type(url);\n}\n\n/*\n * Wrapper for a_Capi_set_vsource_url().\n */\nvoid a_Nav_set_vsource_url(const DilloUrl *Url)\n{\n   a_Capi_set_vsource_url(Url);\n}\n"
  },
  {
    "path": "src/nav.h",
    "content": "#ifndef __NAV_H__\n#define __NAV_H__\n\n#include \"bw.h\"\n\n/*\n * useful macros for the navigation stack\n */\n#define NAV_UIDX(bw, i)       a_Nav_get_uidx(bw, i)\n#define NAV_TOP_UIDX(bw)      a_Nav_get_top_uidx(bw)\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\nvoid a_Nav_redirection0(BrowserWindow *bw, const DilloUrl *new_url);\nvoid a_Nav_push(BrowserWindow *bw, const DilloUrl *url,\n                const DilloUrl *requester);\nvoid a_Nav_repush(BrowserWindow *bw);\nvoid a_Nav_back(BrowserWindow *bw);\nvoid a_Nav_forw(BrowserWindow *bw);\nvoid a_Nav_home(BrowserWindow *bw);\nvoid a_Nav_reload(BrowserWindow *bw);\nvoid a_Nav_jump(BrowserWindow *bw, int offset, int new_bw);\nvoid a_Nav_free(BrowserWindow *bw);\nvoid a_Nav_cancel_expect (BrowserWindow *bw);\nvoid a_Nav_cancel_expect_if_eq(BrowserWindow *bw, const DilloUrl *url);\nvoid a_Nav_expect_done(BrowserWindow *bw);\nint a_Nav_stack_ptr(BrowserWindow *bw);\nint a_Nav_stack_size(BrowserWindow *bw);\nint a_Nav_get_uidx(BrowserWindow *bw, int i);\nint a_Nav_get_top_uidx(BrowserWindow *bw);\n\nvoid a_Nav_save_url(BrowserWindow *bw,\n                    const DilloUrl *url, const char *filename);\nint a_Nav_get_buf(const DilloUrl *Url, char **PBuf, int *BufSize);\nvoid a_Nav_unref_buf(const DilloUrl *Url);\nconst char *a_Nav_get_content_type(const DilloUrl *url);\nvoid a_Nav_set_vsource_url(const DilloUrl *Url);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __NAV_H__ */\n\n\n"
  },
  {
    "path": "src/paths.cc",
    "content": "/*\n * File: paths.cc\n *\n * Copyright 2006-2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <unistd.h>\n#include <errno.h>\n#include <sys/stat.h>\n\n#include \"msg.h\"\n#include \"../dlib/dlib.h\"\n#include \"paths.hh\"\n\n#include   <fcntl.h>\n\n/*\n * Local data\n */\n\n// Dillo works from an unmounted directory (/tmp)\nstatic char* oldWorkingDir = NULL;\n\n/*\n * Changes current working directory to /tmp and creates home config dir\n * if not exists.\n */\nvoid Paths::init(void)\n{\n   char *path;\n   struct stat st;\n   int rc = 0;\n\n   dFree(oldWorkingDir);\n   oldWorkingDir = dGetcwd();\n   rc = chdir(\"/tmp\");\n   if (rc == -1) {\n      MSG(\"paths: Error changing directory to /tmp: %s\\n\",\n          dStrerror(errno));\n   }\n\n   path = dStrconcat(dGethomedir(), \"/.\" BINNAME, NULL);\n   if (stat(path, &st) == -1) {\n      if (errno == ENOENT) {\n         MSG(\"paths: Creating directory '%s/'\\n\", path);\n         if (mkdir(path, 0700) < 0) {\n            MSG(\"paths: Error creating directory %s: %s\\n\",\n                path, dStrerror(errno));\n         }\n      } else {\n         MSG(\"Dillo: error reading %s: %s\\n\", path, dStrerror(errno));\n      }\n   }\n\n   dFree(path);\n}\n\n/*\n * Return the initial current working directory in a string.\n */\nchar *Paths::getOldWorkingDir(void)\n{\n   return oldWorkingDir;\n}\n\n/*\n * Free memory\n */\nvoid Paths::free(void)\n{\n   dFree(oldWorkingDir);\n}\n\n/*\n * Examines the path for \"rcFile\" and assign its file pointer to \"fp\".\n */\nFILE *Paths::getPrefsFP(const char *rcFile)\n{\n   FILE *fp;\n   char *path = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/\", rcFile, NULL);\n   char *path2 = dStrconcat(DILLO_SYSCONF, rcFile, NULL);\n\n   if (!(fp = fopen(path, \"r\"))) {\n      copy_file(path2, path);\n        if (!(fp = fopen(path, \"r\"))) {\n         MSG(\"paths: Using internal defaults because could not make '%s': %s\\n\", path, dStrerror(errno));\n      }\n   }\n\n   dFree(path);\n   dFree(path2);\n\n   return fp;\n}\n\nvoid Paths::copy_file( char *filename, char *outfile){\n   int     in_fd, out_fd, n_chars;\n   char    buf[BUFFERSIZE];\n\n   if ( (in_fd=open(filename, O_RDONLY)) == -1 )\n      MSG(\"Cannot open %s: %s\", filename,dStrerror(errno));\n\n   if ( (out_fd=creat( outfile, COPYMODE)) == -1 )\n      MSG( \"Cannot create %s: %s\", outfile, dStrerror(errno));\n\n   while ( (n_chars = read(in_fd , buf, BUFFERSIZE)) > 0 )\n\n   if ( write( out_fd, buf, n_chars ) != n_chars )\n      MSG(\"Write error to %s: %s\", outfile, dStrerror(errno));\n   if ( n_chars == -1 )\n      MSG(\"Read error from %s: %s\", outfile, dStrerror(errno));\n   if ( close(in_fd) == -1 || close(out_fd) == -1 )\n      MSG(\"Error closing files %s\", dStrerror(errno));\n}\n"
  },
  {
    "path": "src/paths.hh",
    "content": "/*\n * File: paths.hh\n *\n * Copyright 2006-2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef __PATHS_HH__\n#define __PATHS_HH__\n\n#define PATHS_RC_PREFS  \"dillorc\"\n#define PATHS_RC_KEYS   \"keysrc\"\n#define PATHS_RC_DOMAIN \"domainrc\"\n#define PATHS_HSTS_PRELOAD \"hsts_preload\"\n#define PATHS_BM \"bm.txt\"\n#define BUFFERSIZE      4096\n#define COPYMODE        0644\n\nclass Paths {\npublic:\n   static void init(void);\n   static void free(void);\n   static char *getOldWorkingDir(void);\n   static FILE *getPrefsFP(const char *rcFile);\n   static void copy_file(char *, char*);\n};\n\n#endif /* __PATHS_HH__ */\n"
  },
  {
    "path": "src/pixmaps.alt.h",
    "content": "/*\n * File: pixmaps.h\n *\n * Copyright (C) 2000, 2001 Jorge Arellano Cid <jcid@dillo.org>\n * Design by John Grantham, Dipl.-Designer; http://www.grantham.de/\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef __PIXMAPS_H__\n#define __PIXMAPS_H__\n\n/* XPM */\nstatic const char *const left_xpm[] = {\n\"22 22 46 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #56C4FF\",\n\"@\tc #61C6FF\",\n\"#\tc #25B1FF\",\n\"$\tc #68C5FF\",\n\"%\tc #36B2FF\",\n\"&\tc #33B1FF\",\n\"*\tc #6BC1FF\",\n\"=\tc #39ADFF\",\n\"-\tc #119CFF\",\n\";\tc #61BDFF\",\n\">\tc #85CCFF\",\n\",\tc #59BAFF\",\n\"'\tc #6EBFFF\",\n\")\tc #3DA9FF\",\n\"!\tc #1697FF\",\n\"~\tc #0F94FF\",\n\"{\tc #0E88EC\",\n\"]\tc #4CA8FF\",\n\"^\tc #3B9FFF\",\n\"/\tc #1A8FFF\",\n\"(\tc #148DFF\",\n\"_\tc #1382EC\",\n\":\tc #3278C5\",\n\"<\tc #156BCB\",\n\"[\tc #1982F8\",\n\"}\tc #1A86FF\",\n\"|\tc #187CEC\",\n\"1\tc #31619F\",\n\"2\tc #1863C5\",\n\"3\tc #1E7CF8\",\n\"4\tc #1F80FF\",\n\"5\tc #1D77EC\",\n\"6\tc #325C9F\",\n\"7\tc #1C5EC5\",\n\"8\tc #2377F8\",\n\"9\tc #14438C\",\n\"0\tc #0C2A58\",\n\"a\tc #134189\",\n\"b\tc #35599F\",\n\"c\tc #205AC5\",\n\"d\tc #1E54B9\",\n\"e\tc #395AA4\",\n\"f\tc #2356C7\",\n\"g\tc #1C3E93\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"        ..            \",\n\"       .+.            \",\n\"      .@#.            \",\n\"     .$%&...... ....  \",\n\"    .*=-=;>>>,. .,,.  \",\n\"   .')!~~~~~~{. .{{.  \",\n\"  .]^/(((((((_. .__.  \",\n\"  .:<[}}}}}}}|. .||.  \",\n\"   .1234444445. .55.  \",\n\"    .67879000a. .aa.  \",\n\"     .bcd...... ....  \",\n\"      .ef.            \",\n\"       .g.            \",\n\"        ..            \",\n\"                 .....\",\n\"                  .>. \",\n\"                   .  \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const right_xpm[] = {\n\"22 22 46 1\",\n\" \tc None\",\n\".\tc #333333\",\n\"+\tc #7497B2\",\n\"@\tc #307DB7\",\n\"#\tc #809EB6\",\n\"$\tc #3D89C1\",\n\"%\tc #428BC0\",\n\"&\tc #84A5BE\",\n\"*\tc #749FBD\",\n\"=\tc #99B8CE\",\n\"-\tc #7BA4C0\",\n\";\tc #4490C5\",\n\">\tc #0A7CCD\",\n\",\tc #84A9C4\",\n\"'\tc #007BD3\",\n\")\tc #0081DC\",\n\"!\tc #0A83D9\",\n\"~\tc #4A95CB\",\n\"{\tc #84AECB\",\n\"]\tc #0083DE\",\n\"^\tc #0088E8\",\n\"/\tc #0A8BE6\",\n\"(\tc #4397D3\",\n\"_\tc #5E9FCB\",\n\":\tc #008AEA\",\n\"<\tc #0091F5\",\n\"[\tc #008FF0\",\n\"}\tc #007FD7\",\n\"|\tc #2D8ACA\",\n\"1\tc #0090F6\",\n\"2\tc #0299FF\",\n\"3\tc #0096FC\",\n\"4\tc #0084DD\",\n\"5\tc #2F7FB8\",\n\"6\tc #0073BF\",\n\"7\tc #005F9F\",\n\"8\tc #0072C2\",\n\"9\tc #008AE6\",\n\"0\tc #099CFF\",\n\"a\tc #2F85BF\",\n\"b\tc #008AE8\",\n\"c\tc #0090F0\",\n\"d\tc #2D88C6\",\n\"e\tc #0095F9\",\n\"f\tc #2C8ED0\",\n\"g\tc #0080D7\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"            ..        \",\n\"            .+.       \",\n\"            .@#.      \",\n\"  .... ......$%&.     \",\n\"  .**. .*===-;>;,.    \",\n\"  .''. .'))))))!~{.   \",\n\"  .]]. .]^^^^^^^/(_.  \",\n\"  .::. .:<<<<<<<[}|.  \",\n\"  .11. .1222222345.   \",\n\"  .66. .67778909a.    \",\n\"  .... ......bcd.     \",\n\"            .ef.      \",\n\"            .g.       \",\n\"            ..        \",\n\"                 .....\",\n\"                  .=. \",\n\"                   .  \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const reload_xpm[] = {\n\"20 20 18 1\",\n\" \tc None\",\n\".\tc #5C7FCE\",\n\"+\tc #6686CE\",\n\"@\tc #6586CE\",\n\"#\tc #5379CD\",\n\"$\tc #7297E5\",\n\"%\tc #3D6ACF\",\n\"&\tc #7992C9\",\n\"*\tc #6484CE\",\n\"=\tc #79A2FF\",\n\"-\tc #769DF0\",\n\";\tc #6284CE\",\n\">\tc #6A8ACF\",\n\",\tc #436ECE\",\n\"'\tc #5E81CE\",\n\")\tc #3B68D0\",\n\"!\tc #7891C9\",\n\"~\tc #6C8ACD\",\n\"                    \",\n\"                    \",\n\"        .+@.#       \",\n\"      ++++++++$  %  \",\n\"     ++++&&&&+++%%  \",\n\"    ++&+++&&&&&+&%  \",\n\"   *+&%++%%%&&&+&%  \",\n\"   ++++=    %%&++%  \",\n\"  %+++=    %%&&&&%  \",\n\"  %++-    %%%%%%%%  \",\n\"  &+++              \",\n\"  &+;+              \",\n\"  &++               \",\n\"  %++%          >   \",\n\"   %&%%        >    \",\n\"    %&&,'    %>>    \",\n\"    )&&!~  %%>>=    \",\n\"     %%%&&&&>>=     \",\n\"       =>>>         \",\n\"                    \"};\n\n/* XPM */\nstatic const char *const home_xpm[] = {\n\"20 20 5 1\",\n\" \tc None\",\n\".\tc #0000FF\",\n\"+\tc #9292DB\",\n\"@\tc #DBFFFF\",\n\"#\tc #494992\",\n\"                    \",\n\"                    \",\n\"         ..         \",\n\"        ....+       \",\n\"       ......+      \",\n\"      ........+     \",\n\"     ..........+    \",\n\"    .....+ .....+   \",\n\"   .....+   .....+  \",\n\"  .....+     .....+ \",\n\" .....+       .....+\",\n\" ....+   ++++  ....+\",\n\"  ...    +@@+   ..+ \",\n\"    .    +@@+    .  \",\n\"    .    +++     .  \",\n\"    .    +##     .  \",\n\"    .    +###    .  \",\n\"    .....+........  \",\n\"                    \",\n\"                    \"\n};\n\n/* XPM */\nstatic const char *const save_xpm[] = {\n\"22 22 62 1\",\n\" \tc None\",\n\".\tc #5959CB\",\n\"+\tc #6D6DD0\",\n\"@\tc #6C6CD0\",\n\"#\tc #6A6ACF\",\n\"$\tc #6A6AD0\",\n\"%\tc #464646\",\n\"&\tc #2D2DAC\",\n\"*\tc #798D79\",\n\"=\tc #BDD0BD\",\n\"-\tc #4242C6\",\n\";\tc #297DA2\",\n\">\tc #0003DC\",\n\",\tc #0720CE\",\n\"'\tc #1347C4\",\n\")\tc #6464CE\",\n\"!\tc #236CAE\",\n\"~\tc #0415D3\",\n\"{\tc #0000DC\",\n\"]\tc #1E61B7\",\n\"^\tc #5D5DCB\",\n\"/\tc #0414D5\",\n\"(\tc #0412D6\",\n\"_\tc #0C29CB\",\n\":\tc #081ED1\",\n\"<\tc #4C4CC8\",\n\"[\tc #1F50B1\",\n\"}\tc #0716D4\",\n\"|\tc #3F873F\",\n\"1\tc #0304DA\",\n\"2\tc #0B21D0\",\n\"3\tc #0410D8\",\n\"4\tc #081DD3\",\n\"5\tc #2354AF\",\n\"6\tc #0C21CB\",\n\"7\tc #6868CF\",\n\"8\tc #5353C9\",\n\"9\tc #6968CF\",\n\"0\tc #6363CD\",\n\"a\tc #1E41B2\",\n\"b\tc #A5A5A5\",\n\"c\tc #B8B8B8\",\n\"d\tc #C8C8C8\",\n\"e\tc #B4B4B4\",\n\"f\tc #868686\",\n\"g\tc #848484\",\n\"h\tc #939393\",\n\"i\tc #A6A6A6\",\n\"j\tc #3838BE\",\n\"k\tc #9E9E9E\",\n\"l\tc #6060CC\",\n\"m\tc #B5B5B5\",\n\"n\tc #6B6AD0\",\n\"o\tc #D0D0D0\",\n\"p\tc #CDCDCD\",\n\"q\tc #C4C4C4\",\n\"r\tc #BABABA\",\n\"s\tc #0C1DCB\",\n\"t\tc #487C8A\",\n\"u\tc #B1B1B1\",\n\"v\tc #919191\",\n\"w\tc #6666CE\",\n\"                      \",\n\"                      \",\n\"   .+++++@@##@@++$%   \",\n\"  & *============* $  \",\n\"  -;>            ,')  \",\n\"  -!~            {].  \",\n\"  ^ >            { -  \",\n\"  $ /            { #  \",\n\"  - (            > ^  \",\n\"  ^ _            : <  \",\n\"  + [            } &  \",\n\"  +  ||1{122234>5  .  \",\n\"  + 6#@77..8<^9^0a +  \",\n\"  + &bbbcddddddde@ +  \",\n\"  + ^f^^g       h@ +  \",\n\"  + #i&jkiiiiiiikl -  \",\n\"  @ )m^^ mmmmmmm n ^  \",\n\"  - $o&%pqqqqqqqr7s^  \",\n\"  ^t-uuuh       v<.   \",\n\"   ..)#)..l^$$##w$    \",\n\"                      \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const stop_xpm[] = {\n\"22 22 77 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #703434\",\n\"@      c #E57F7F\",\n\"#      c #E68080\",\n\"$      c #6F2929\",\n\"%      c #DB4343\",\n\"&      c #D31A1A\",\n\"*      c #CE0000\",\n\"=      c #DC4343\",\n\"-      c #D51A1A\",\n\";      c #D00000\",\n\">      c #702929\",\n\",      c #DF4343\",\n\"'      c #D71A1A\",\n\")      c #D30000\",\n\"!      c #6F1B1B\",\n\"~      c #E14343\",\n\"{      c #DA1A1A\",\n\"]      c #D60000\",\n\"^      c #A66666\",\n\"/      c #841717\",\n\"(      c #DA0404\",\n\"_      c #DD1A1A\",\n\":      c #D90000\",\n\"<      c #D56B6B\",\n\"[      c #FFFFFF\",\n\"}      c #E7D5D5\",\n\"|      c #861717\",\n\"1      c #CC0000\",\n\"2      c #DC0000\",\n\"3      c #E64C4C\",\n\"4      c #FADEDE\",\n\"5      c #EAD5D5\",\n\"6      c #722C2C\",\n\"7      c #CF0000\",\n\"8      c #E00000\",\n\"9      c #EB5C5C\",\n\"0      c #FBDFDF\",\n\"a      c #FDF8F8\",\n\"b      c #E40000\",\n\"c      c #DE3535\",\n\"d      c #FEF8F8\",\n\"e      c #E70000\",\n\"f      c #971C1C\",\n\"g      c #EED5D5\",\n\"h      c #FEFAFA\",\n\"i      c #EB0000\",\n\"j      c #AA1C1C\",\n\"k      c #EFD5D5\",\n\"l      c #FDE3E3\",\n\"m      c #F69595\",\n\"n      c #C50000\",\n\"o      c #D50000\",\n\"p      c #EF0000\",\n\"q      c #F77777\",\n\"r      c #FDE4E4\",\n\"s      c #F56565\",\n\"t      c #4D0000\",\n\"u      c #9E0000\",\n\"v      c #D70000\",\n\"w      c #F20000\",\n\"x      c #FAA7A7\",\n\"y      c #F76969\",\n\"z      c #420000\",\n\"A      c #A00000\",\n\"B      c #DA0000\",\n\"C      c #F50000\",\n\"D      c #A20000\",\n\"E      c #DD0000\",\n\"F      c #F80000\",\n\"G      c #430000\",\n\"H      c #A40000\",\n\"I      c #DF0000\",\n\"J      c #FB0000\",\n\"K      c #380000\",\n\"L      c #570000\",\n\"                      \",\n\"                      \",\n\"      .........       \",\n\"     .+@#####@+.      \",\n\"    .$%&*****&%$.     \",\n\"   .$=-;;;;;;;-=$.    \",\n\"  .>,')))))))))',>.   \",\n\" .!~{]]^/]]]/^]]{~!.  \",\n\" .(_::<[}|:|}[<::_(.  \",\n\" .122234[565[432221.  \",\n\" .7888890[a[0988887.  \",\n\" .)bbbbbcd[dcbbbbb).  \",\n\" .]eeeefg[h[gfeeee].  \",\n\" .:iiijk[lml[kjiii:.  \",\n\" .noppq[rspsr[qppon.  \",\n\" .tuvwwxywwwyxwwvut.  \",\n\"  .zABCCCCCCCCCBAz.   \",\n\"   .zDEFFFFFFFEDz.    \",\n\"    .GHIJJJJJIHG.     \",\n\"     .KLLLLLLLK.      \",\n\"      .........       \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const bm_xpm[] = {\n\"20 20 285 2\",\n\"  \tc None\",\n\". \tc #E1DDE3\",\n\"+ \tc #CCCCD5\",\n\"@ \tc #BFC4CB\",\n\"# \tc #BAC1CB\",\n\"$ \tc #C1C9D1\",\n\"% \tc #C5CBD5\",\n\"& \tc #C5CCD6\",\n\"* \tc #C2CBD6\",\n\"= \tc #B9C7D1\",\n\"- \tc #B8C7D2\",\n\"; \tc #B5C6D0\",\n\"> \tc #B4C5CF\",\n\", \tc #B8C7D0\",\n\"' \tc #B4C1CC\",\n\") \tc #ABB8C3\",\n\"! \tc #96A1AC\",\n\"~ \tc #C5C2CA\",\n\"{ \tc #BBBCC8\",\n\"] \tc #D9E0E5\",\n\"^ \tc #E2EEF1\",\n\"/ \tc #E0EAF1\",\n\"( \tc #DEE8F1\",\n\"_ \tc #DAE7F1\",\n\": \tc #D7E6F0\",\n\"< \tc #D5E7F1\",\n\"[ \tc #D0E5F2\",\n\"} \tc #CCE3F4\",\n\"| \tc #CDE7F5\",\n\"1 \tc #CBE2F3\",\n\"2 \tc #CFE2F3\",\n\"3 \tc #DAE7F3\",\n\"4 \tc #798FA9\",\n\"5 \tc #B5B4BF\",\n\"6 \tc #AEAFBB\",\n\"7 \tc #959BAB\",\n\"8 \tc #95A4B7\",\n\"9 \tc #98A8BF\",\n\"0 \tc #97A7BE\",\n\"a \tc #8E9FBC\",\n\"b \tc #7C93BA\",\n\"c \tc #7492BC\",\n\"d \tc #618DBF\",\n\"e \tc #4E8CC5\",\n\"f \tc #268CD6\",\n\"g \tc #3089CD\",\n\"h \tc #4A88C1\",\n\"i \tc #C7D5E7\",\n\"j \tc #748AAE\",\n\"k \tc #B4B2BE\",\n\"l \tc #BDBECA\",\n\"m \tc #A2A9B7\",\n\"n \tc #ADBCCF\",\n\"o \tc #B0C2D7\",\n\"p \tc #A9BCD2\",\n\"q \tc #A8B9D0\",\n\"r \tc #A1B3CB\",\n\"s \tc #9BB0CA\",\n\"t \tc #91AAC7\",\n\"u \tc #86A6C8\",\n\"v \tc #82A4C7\",\n\"w \tc #7B9DC4\",\n\"x \tc #6B96C7\",\n\"y \tc #CEDFF5\",\n\"z \tc #758EB4\",\n\"A \tc #AEACB8\",\n\"B \tc #B6B7C2\",\n\"C \tc #9CA5B3\",\n\"D \tc #A8BACF\",\n\"E \tc #A9BFD7\",\n\"F \tc #A2B6CE\",\n\"G \tc #A1B4CB\",\n\"H \tc #9BAEC5\",\n\"I \tc #94ACC5\",\n\"J \tc #8BA9C7\",\n\"K \tc #7BA1CA\",\n\"L \tc #7FA3CB\",\n\"M \tc #779DC9\",\n\"N \tc #6292CC\",\n\"O \tc #CBDBF5\",\n\"P \tc #7C92B5\",\n\"Q \tc #A2A1AD\",\n\"R \tc #AEB1B8\",\n\"S \tc #949FAC\",\n\"T \tc #A0B4C8\",\n\"U \tc #A6BCD2\",\n\"V \tc #A0B4CB\",\n\"W \tc #A9BCD0\",\n\"X \tc #B1C3D7\",\n\"Y \tc #9EB4CE\",\n\"Z \tc #7C9EC4\",\n\"` \tc #86A8CB\",\n\" .\tc #6B98CB\",\n\"..\tc #759BC9\",\n\"+.\tc #6B93CB\",\n\"@.\tc #CBDBF7\",\n\"#.\tc #5E7BB8\",\n\"$.\tc #A09FA9\",\n\"%.\tc #ACAEB4\",\n\"&.\tc #8C9BA6\",\n\"*.\tc #96ABBC\",\n\"=.\tc #A2B6CB\",\n\"-.\tc #9DB0C5\",\n\";.\tc #B0C0D0\",\n\">.\tc #C9D9E4\",\n\",.\tc #B9CAD9\",\n\"'.\tc #86A1BC\",\n\").\tc #7B9FC5\",\n\"!.\tc #789CC8\",\n\"~.\tc #7B9CC8\",\n\"{.\tc #6190CD\",\n\"].\tc #D0E0FA\",\n\"^.\tc #5A79B9\",\n\"/.\tc #9D9AA5\",\n\"(.\tc #A5A6AA\",\n\"_.\tc #86969F\",\n\":.\tc #9FB4C3\",\n\"<.\tc #A2B8CB\",\n\"[.\tc #8EA4B8\",\n\"}.\tc #B4C4D2\",\n\"|.\tc #DBEAF3\",\n\"1.\tc #C2D4E2\",\n\"2.\tc #7598B7\",\n\"3.\tc #6F99C1\",\n\"4.\tc #7098C6\",\n\"5.\tc #6D92C4\",\n\"6.\tc #688CC9\",\n\"7.\tc #CCD9F6\",\n\"8.\tc #5B78B8\",\n\"9.\tc #969297\",\n\"0.\tc #9D9B9D\",\n\"a.\tc #818B9C\",\n\"b.\tc #ABBED1\",\n\"c.\tc #D8ECF8\",\n\"d.\tc #E9FAFF\",\n\"e.\tc #EBF9FE\",\n\"f.\tc #ECF9FF\",\n\"g.\tc #EDFBFE\",\n\"h.\tc #EBFEFE\",\n\"i.\tc #DBF2FB\",\n\"j.\tc #B2CBE3\",\n\"k.\tc #6992C3\",\n\"l.\tc #5587CF\",\n\"m.\tc #D1E1F8\",\n\"n.\tc #6E85B8\",\n\"o.\tc #958E8B\",\n\"p.\tc #999698\",\n\"q.\tc #75819E\",\n\"r.\tc #8FA2BC\",\n\"s.\tc #ABBDCE\",\n\"t.\tc #C5D7E1\",\n\"u.\tc #E3F1F8\",\n\"v.\tc #EEFAFF\",\n\"w.\tc #E6F5FB\",\n\"x.\tc #D1E3ED\",\n\"y.\tc #A3BAD0\",\n\"z.\tc #6C97C6\",\n\"A.\tc #7195C4\",\n\"B.\tc #5186CD\",\n\"C.\tc #CEDDF2\",\n\"D.\tc #637CBA\",\n\"E.\tc #90847C\",\n\"F.\tc #918E91\",\n\"G.\tc #6C7A9E\",\n\"H.\tc #8D9FB9\",\n\"I.\tc #809CB9\",\n\"J.\tc #B7CBDE\",\n\"K.\tc #EBF9FF\",\n\"L.\tc #F1FFFF\",\n\"M.\tc #EFFDFF\",\n\"N.\tc #C9E0F1\",\n\"O.\tc #2987D1\",\n\"P.\tc #6692C2\",\n\"Q.\tc #5889CB\",\n\"R.\tc #D2DEF5\",\n\"S.\tc #5474B6\",\n\"T.\tc #8A7673\",\n\"U.\tc #86848C\",\n\"V.\tc #6275A0\",\n\"W.\tc #7B94B4\",\n\"X.\tc #879EBA\",\n\"Y.\tc #C5D7E5\",\n\"Z.\tc #E1F1FA\",\n\"`.\tc #CCDAE7\",\n\" +\tc #D4E4EE\",\n\".+\tc #D4E9F4\",\n\"++\tc #85A5CA\",\n\"@+\tc #6794C4\",\n\"#+\tc #5B8BC6\",\n\"$+\tc #5086CB\",\n\"%+\tc #CDD9F0\",\n\"&+\tc #6F84B2\",\n\"*+\tc #837377\",\n\"=+\tc #7D7989\",\n\"-+\tc #536AA5\",\n\";+\tc #6E8AB5\",\n\">+\tc #9AB0CD\",\n\",+\tc #B7CBDD\",\n\"'+\tc #AABFD5\",\n\")+\tc #7C9DC1\",\n\"!+\tc #8CAACB\",\n\"~+\tc #B0C6DA\",\n\"{+\tc #9BB5D6\",\n\"]+\tc #4B89CD\",\n\"^+\tc #5486CA\",\n\"/+\tc #638EC7\",\n\"(+\tc #C5D4EC\",\n\"_+\tc #6D83B2\",\n\":+\tc #82707F\",\n\"<+\tc #747289\",\n\"[+\tc #4A66A8\",\n\"}+\tc #7792BC\",\n\"|+\tc #8AA7CC\",\n\"1+\tc #85A3CD\",\n\"2+\tc #7A9BC4\",\n\"3+\tc #6992C1\",\n\"4+\tc #6693C6\",\n\"5+\tc #769ECF\",\n\"6+\tc #6A9BDA\",\n\"7+\tc #6091CF\",\n\"8+\tc #4C84D1\",\n\"9+\tc #4E72D7\",\n\"0+\tc #CFDFF6\",\n\"a+\tc #3F67CB\",\n\"b+\tc #7A6A7D\",\n\"c+\tc #6B6A89\",\n\"d+\tc #3E5FAD\",\n\"e+\tc #7A93BF\",\n\"f+\tc #7B9BC9\",\n\"g+\tc #638FC8\",\n\"h+\tc #6C92C7\",\n\"i+\tc #6F96C9\",\n\"j+\tc #6292CD\",\n\"k+\tc #5890D1\",\n\"l+\tc #4D8AD5\",\n\"m+\tc #598DD2\",\n\"n+\tc #4982D2\",\n\"o+\tc #4978DA\",\n\"p+\tc #D6E4F8\",\n\"q+\tc #486AC5\",\n\"r+\tc #5E3782\",\n\"s+\tc #63628C\",\n\"t+\tc #395CAF\",\n\"u+\tc #748EBD\",\n\"v+\tc #7E9BC5\",\n\"w+\tc #7093C5\",\n\"x+\tc #7093C4\",\n\"y+\tc #6B92C6\",\n\"z+\tc #608FCA\",\n\"A+\tc #5A8ECD\",\n\"B+\tc #5A8BCD\",\n\"C+\tc #5586CD\",\n\"D+\tc #6289C9\",\n\"E+\tc #4E7DC9\",\n\"F+\tc #A6B4CF\",\n\"G+\tc #687CB2\",\n\"H+\tc #948BA3\",\n\"I+\tc #63689A\",\n\"J+\tc #4566AD\",\n\"K+\tc #6F86B4\",\n\"L+\tc #7A91B9\",\n\"M+\tc #768FB9\",\n\"N+\tc #738FB9\",\n\"O+\tc #6D8DBB\",\n\"P+\tc #6489BE\",\n\"Q+\tc #5F87C0\",\n\"R+\tc #5D86C1\",\n\"S+\tc #5D83C0\",\n\"T+\tc #6D86B9\",\n\"U+\tc #647EB4\",\n\"V+\tc #6A7BAA\",\n\"W+\tc #6F80AD\",\n\"X+\tc #6975A1\",\n\"Y+\tc #4C6DAC\",\n\"Z+\tc #6C81AF\",\n\"`+\tc #7085B0\",\n\" @\tc #7187B1\",\n\".@\tc #7089B2\",\n\"+@\tc #6D88B3\",\n\"@@\tc #6485B5\",\n\"#@\tc #6183B8\",\n\"$@\tc #5F82B9\",\n\"%@\tc #5B7DB8\",\n\"&@\tc #6882B2\",\n\"*@\tc #7586AD\",\n\"                                        \",\n\"                                        \",\n\"    . + @ # $ % & * = - ; > , ' ) !     \",\n\"    ~ { ] ^ / ( _ : < [ } | 1 2 3 4     \",\n\"    5 6 7 8 9 0 a b c d e f g h i j     \",\n\"    k l m n o p q r s t u v w x y z     \",\n\"    A B C D E F G H I J K L M N O P     \",\n\"    Q R S T U V W X Y Z `  ...+.@.#.    \",\n\"    $.%.&.*.=.-.;.>.,.'.).!.~.{.].^.    \",\n\"    /.(._.:.<.[.}.|.1.2.3.4.5.6.7.8.    \",\n\"    9.0.a.b.c.d.e.f.g.h.i.j.k.l.m.n.    \",\n\"    o.p.q.r.s.t.u.v.w.x.y.z.A.B.C.D.    \",\n\"    E.F.G.H.I.J.K.L.M.N.O.P.k.Q.R.S.    \",\n\"    T.U.V.W.X.Y.Z.`. +.+++@+#+$+%+&+    \",\n\"    *+=+-+;+>+,+'+)+!+~+{+]+^+/+(+_+    \",\n\"    :+<+[+}+|+1+2+3+4+5+6+7+8+9+0+a+    \",\n\"    b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+    \",\n\"    r+s+t+u+v+w+x+y+z+A+B+C+D+E+F+G+    \",\n\"    H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+    \",\n\"      X+Y+Z+`+ @.@+@@@#@$@%@&@*@        \"};\n\n/* XPM */\nstatic const char *const tools_xpm[] = {\n\"22 22 75 1\",\n\" \tc None\",\n\".\tc #5877CB\",\n\"+\tc #5A79CA\",\n\"@\tc #5978CA\",\n\"#\tc #5A79CC\",\n\"$\tc #637FC8\",\n\"%\tc #5D7CCB\",\n\"&\tc #5876CB\",\n\"*\tc #5D7BC9\",\n\"=\tc #6482D0\",\n\"-\tc #647FC6\",\n\";\tc #A2A6CC\",\n\">\tc #617FC8\",\n\",\tc #5A7ACC\",\n\"'\tc #8094CA\",\n\")\tc #5978CB\",\n\"!\tc #5F7CC9\",\n\"~\tc #607DC8\",\n\"{\tc #5A79CB\",\n\"]\tc #5B7ACB\",\n\"^\tc #5C7BCA\",\n\"/\tc #5274CC\",\n\"(\tc #879BCC\",\n\"_\tc #5A7BCF\",\n\":\tc #6782C9\",\n\"<\tc #6480C8\",\n\"[\tc #5B7BCB\",\n\"}\tc #6280CC\",\n\"|\tc #6681C6\",\n\"1\tc #7D94CC\",\n\"2\tc #5777CD\",\n\"3\tc #5676CB\",\n\"4\tc #5375CC\",\n\"5\tc #92A3D2\",\n\"6\tc #5777CC\",\n\"7\tc #6683CC\",\n\"8\tc #6883C9\",\n\"9\tc #B8C6E9\",\n\"0\tc #7B91CC\",\n\"a\tc #6883C7\",\n\"b\tc #8397CC\",\n\"c\tc #B7C5E6\",\n\"d\tc #758CC7\",\n\"e\tc #8594CC\",\n\"f\tc #617EC7\",\n\"g\tc #778DC8\",\n\"h\tc #5676CC\",\n\"i\tc #90A2D0\",\n\"j\tc #7189C6\",\n\"k\tc #5F7BC9\",\n\"l\tc #446BD2\",\n\"m\tc #96A8D3\",\n\"n\tc #6F88C6\",\n\"o\tc #607CC8\",\n\"p\tc #7C94CD\",\n\"q\tc #7990CC\",\n\"r\tc #758ECC\",\n\"s\tc #758DCC\",\n\"t\tc #6D85C6\",\n\"u\tc #6580C6\",\n\"v\tc #7C92CA\",\n\"w\tc #7F96D2\",\n\"x\tc #6983C6\",\n\"y\tc #6882C6\",\n\"z\tc #8297CE\",\n\"A\tc #6E89CC\",\n\"B\tc #7189C5\",\n\"C\tc #8699CA\",\n\"D\tc #758BC6\",\n\"E\tc #6481CA\",\n\"F\tc #6482CC\",\n\"G\tc #708ACC\",\n\"H\tc #748DCD\",\n\"I\tc #5D7BCC\",\n\"J\tc #5C7ED3\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"         .++@         \",\n\"         #$%&*        \",\n\"         =-;;>@       \",\n\"          ,';;;@      \",\n\"    @     )!~;;;+     \",\n\"   @@{     @+;;;]     \",\n\"   @^;)     /(;;#_    \",\n\"   @:;;+    <;;;[}    \",\n\"   @|;;123^45;;;67    \",\n\"    8;;;90abc;;;de    \",\n\"    ^^f;;;;;;;;;g     \",\n\"     ~hi;;;;;;;;;j    \",\n\"      klm;;;;;;;;no   \",\n\"       ^hpqrstu;;;;v  \",\n\"             wxy;;;z  \",\n\"              A~BCDE  \",\n\"               FGHIJ  \",\n\"                      \",\n\"                      \"};\n/* XPM */\nstatic const char *const newtab_xpm[] = {\n\"22 22 75 1\",\n\" \tc None\",\n\"B\tc #5877CB\",\n\"C\tc #5A79CA\",\n\"D\tc #5978CA\",\n\"E\tc #5A79CC\",\n\"F\tc #637FC8\",\n\"G\tc #5D7CCB\",\n\"H\tc #5876CB\",\n\"I\tc #5D7BC9\",\n\"J\tc #6482D0\",\n\"K\tc #647FC6\",\n\"L\tc #A2A6CC\",\n\"M\tc #617FC8\",\n\"N\tc #5A7ACC\",\n\"O\tc #8094CA\",\n\"P\tc #5978CB\",\n\"Q\tc #5F7CC9\",\n\"R\tc #607DC8\",\n\"S\tc #5A79CB\",\n\"T\tc #5B7ACB\",\n\"U\tc #5C7BCA\",\n\"V\tc #5274CC\",\n\"W\tc #879BCC\",\n\"X\tc #5A7BCF\",\n\"Y\tc #6782C9\",\n\"Z\tc #6480C8\",\n\"a\tc #5B7BCB\",\n\"b\tc #6280CC\",\n\"c\tc #6681C6\",\n\"d\tc #7D94CC\",\n\"e\tc #5777CD\",\n\"f\tc #5676CB\",\n\"g\tc #5375CC\",\n\"h\tc #92A3D2\",\n\"i\tc #5777CC\",\n\"j\tc #6683CC\",\n\"k\tc #6883C9\",\n\"l\tc #B8C6E9\",\n\"m\tc #7B91CC\",\n\"n\tc #6883C7\",\n\"o\tc #8397CC\",\n\"p\tc #B7C5E6\",\n\"q\tc #758CC7\",\n\"r\tc #8594CC\",\n\"s\tc #617EC7\",\n\"t\tc #778DC8\",\n\"u\tc #5676CC\",\n\"v\tc #90A2D0\",\n\"w\tc #7189C6\",\n\"x\tc #5F7BC9\",\n\"y\tc #446BD2\",\n\"z\tc #96A8D3\",\n\"0\tc #6F88C6\",\n\"1\tc #607CC8\",\n\"2\tc #7C94CD\",\n\"3\tc #7990CC\",\n\"4\tc #758ECC\",\n\"5\tc #758DCC\",\n\"6\tc #6D85C6\",\n\"7\tc #6580C6\",\n\"8\tc #7C92CA\",\n\"9\tc #7F96D2\",\n\"+\tc #6983C6\",\n\"/\tc #6882C6\",\n\"!\tc #8297CE\",\n\"#\tc #6E89CC\",\n\"$\tc #7189C5\",\n\"%\tc #8699CA\",\n\"&\tc #758BC6\",\n\"'\tc #6481CA\",\n\"(\tc #6482CC\",\n\")\tc #708ACC\",\n\"*\tc #748DCD\",\n\",\tc #5D7BCC\",\n\"-\tc #5C7ED3\",\n\"                      \",\n\"      BBBBBBBBBB      \",\n\"      BLLLLLLLLB      \",\n\"      BLLLLLLLLB      \",\n\"      BLLLLLLLLB      \",\n\"      BLLLLLLLLB      \",\n\"      BLLLLLLLLB      \",\n\" BBBBBBLLLLLLLLBBBBBB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BBBBBBLLLLLLLLBBBBBB \",\n\"      BLLLLLLLLB      \",\n\"      BLLLLLLLLB      \",\n\"      BLLLLLLLLB      \",\n\"      BLLLLLLLLB      \",\n\"      BLLLLLLLLB      \",\n\"      BBBBBBBBBB      \",\n\"                      \"\n};\n\n/* Small icons here */\n\n/* XPM */\nstatic const char *const left_s_xpm[] = {\n\"22 22 46 1\",\n\" \tc None\",\n\".\tc #000000\",\n\"+\tc #56C4FF\",\n\"@\tc #61C6FF\",\n\"#\tc #25B1FF\",\n\"$\tc #68C5FF\",\n\"%\tc #36B2FF\",\n\"&\tc #33B1FF\",\n\"*\tc #6BC1FF\",\n\"=\tc #39ADFF\",\n\"-\tc #119CFF\",\n\";\tc #61BDFF\",\n\">\tc #85CCFF\",\n\",\tc #59BAFF\",\n\"'\tc #6EBFFF\",\n\")\tc #3DA9FF\",\n\"!\tc #1697FF\",\n\"~\tc #0F94FF\",\n\"{\tc #0E88EC\",\n\"]\tc #4CA8FF\",\n\"^\tc #3B9FFF\",\n\"/\tc #1A8FFF\",\n\"(\tc #148DFF\",\n\"_\tc #1382EC\",\n\":\tc #3278C5\",\n\"<\tc #156BCB\",\n\"[\tc #1982F8\",\n\"}\tc #1A86FF\",\n\"|\tc #187CEC\",\n\"1\tc #31619F\",\n\"2\tc #1863C5\",\n\"3\tc #1E7CF8\",\n\"4\tc #1F80FF\",\n\"5\tc #1D77EC\",\n\"6\tc #325C9F\",\n\"7\tc #1C5EC5\",\n\"8\tc #2377F8\",\n\"9\tc #14438C\",\n\"0\tc #0C2A58\",\n\"a\tc #134189\",\n\"b\tc #35599F\",\n\"c\tc #205AC5\",\n\"d\tc #1E54B9\",\n\"e\tc #395AA4\",\n\"f\tc #2356C7\",\n\"g\tc #1C3E93\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"        ..            \",\n\"       .+.            \",\n\"      .@#.            \",\n\"     .$%&...... ....  \",\n\"    .*=-=;>>>,. .,,.  \",\n\"   .')!~~~~~~{. .{{.  \",\n\"  .]^/(((((((_. .__.  \",\n\"  .:<[}}}}}}}|. .||.  \",\n\"   .1234444445. .55.  \",\n\"    .67879000a. .aa.  \",\n\"     .bcd...... ....  \",\n\"      .ef.            \",\n\"       .g.            \",\n\"        ..            \",\n\"                 .....\",\n\"                  .>. \",\n\"                   .  \",\n\"                      \"};\n/* XPM */\nstatic const char *const right_s_xpm[] = {\n\"22 22 46 1\",\n\" \tc None\",\n\".\tc #333333\",\n\"+\tc #7497B2\",\n\"@\tc #307DB7\",\n\"#\tc #809EB6\",\n\"$\tc #3D89C1\",\n\"%\tc #428BC0\",\n\"&\tc #84A5BE\",\n\"*\tc #749FBD\",\n\"=\tc #99B8CE\",\n\"-\tc #7BA4C0\",\n\";\tc #4490C5\",\n\">\tc #0A7CCD\",\n\",\tc #84A9C4\",\n\"'\tc #007BD3\",\n\")\tc #0081DC\",\n\"!\tc #0A83D9\",\n\"~\tc #4A95CB\",\n\"{\tc #84AECB\",\n\"]\tc #0083DE\",\n\"^\tc #0088E8\",\n\"/\tc #0A8BE6\",\n\"(\tc #4397D3\",\n\"_\tc #5E9FCB\",\n\":\tc #008AEA\",\n\"<\tc #0091F5\",\n\"[\tc #008FF0\",\n\"}\tc #007FD7\",\n\"|\tc #2D8ACA\",\n\"1\tc #0090F6\",\n\"2\tc #0299FF\",\n\"3\tc #0096FC\",\n\"4\tc #0084DD\",\n\"5\tc #2F7FB8\",\n\"6\tc #0073BF\",\n\"7\tc #005F9F\",\n\"8\tc #0072C2\",\n\"9\tc #008AE6\",\n\"0\tc #099CFF\",\n\"a\tc #2F85BF\",\n\"b\tc #008AE8\",\n\"c\tc #0090F0\",\n\"d\tc #2D88C6\",\n\"e\tc #0095F9\",\n\"f\tc #2C8ED0\",\n\"g\tc #0080D7\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"            ..        \",\n\"            .+.       \",\n\"            .@#.      \",\n\"  .... ......$%&.     \",\n\"  .**. .*===-;>;,.    \",\n\"  .''. .'))))))!~{.   \",\n\"  .]]. .]^^^^^^^/(_.  \",\n\"  .::. .:<<<<<<<[}|.  \",\n\"  .11. .1222222345.   \",\n\"  .66. .67778909a.    \",\n\"  .... ......bcd.     \",\n\"            .ef.      \",\n\"            .g.       \",\n\"            ..        \",\n\"                 .....\",\n\"                  .=. \",\n\"                   .  \",\n\"                      \"};\n/* XPM */\nstatic const char *const reload_s_xpm[] = {\n\"20 20 18 1\",\n\" \tc None\",\n\".\tc #5C7FCE\",\n\"+\tc #6686CE\",\n\"@\tc #6586CE\",\n\"#\tc #5379CD\",\n\"$\tc #7297E5\",\n\"%\tc #3D6ACF\",\n\"&\tc #7992C9\",\n\"*\tc #6484CE\",\n\"=\tc #79A2FF\",\n\"-\tc #769DF0\",\n\";\tc #6284CE\",\n\">\tc #6A8ACF\",\n\",\tc #436ECE\",\n\"'\tc #5E81CE\",\n\")\tc #3B68D0\",\n\"!\tc #7891C9\",\n\"~\tc #6C8ACD\",\n\"                    \",\n\"                    \",\n\"        .+@.#       \",\n\"      ++++++++$  %  \",\n\"     ++++&&&&+++%%  \",\n\"    ++&+++&&&&&+&%  \",\n\"   *+&%++%%%&&&+&%  \",\n\"   ++++=    %%&++%  \",\n\"  %+++=    %%&&&&%  \",\n\"  %++-    %%%%%%%%  \",\n\"  &+++              \",\n\"  &+;+              \",\n\"  &++               \",\n\"  %++%          >   \",\n\"   %&%%        >    \",\n\"    %&&,'    %>>    \",\n\"    )&&!~  %%>>=    \",\n\"     %%%&&&&>>=     \",\n\"       =>>>         \",\n\"                    \"};\n/* XPM */\nstatic const char *const home_s_xpm[] = {\n\"20 20 5 1\",\n\" \tc None\",\n\".\tc #0000FF\",\n\"+\tc #9292DB\",\n\"@\tc #DBFFFF\",\n\"#\tc #494992\",\n\"                    \",\n\"                    \",\n\"         ..         \",\n\"        ....+       \",\n\"       ......+      \",\n\"      ........+     \",\n\"     ..........+    \",\n\"    .....+ .....+   \",\n\"   .....+   .....+  \",\n\"  .....+     .....+ \",\n\" .....+       .....+\",\n\" ....+   ++++  ....+\",\n\"  ...    +@@+   ..+ \",\n\"    .    +@@+    .  \",\n\"    .    +++     .  \",\n\"    .    +##     .  \",\n\"    .    +###    .  \",\n\"    .....+........  \",\n\"                    \",\n\"                    \"\n};\n/* XPM */\nstatic const char *const save_s_xpm[] = {\n\"22 22 62 1\",\n\" \tc None\",\n\".\tc #5959CB\",\n\"+\tc #6D6DD0\",\n\"@\tc #6C6CD0\",\n\"#\tc #6A6ACF\",\n\"$\tc #6A6AD0\",\n\"%\tc #464646\",\n\"&\tc #2D2DAC\",\n\"*\tc #798D79\",\n\"=\tc #BDD0BD\",\n\"-\tc #4242C6\",\n\";\tc #297DA2\",\n\">\tc #0003DC\",\n\",\tc #0720CE\",\n\"'\tc #1347C4\",\n\")\tc #6464CE\",\n\"!\tc #236CAE\",\n\"~\tc #0415D3\",\n\"{\tc #0000DC\",\n\"]\tc #1E61B7\",\n\"^\tc #5D5DCB\",\n\"/\tc #0414D5\",\n\"(\tc #0412D6\",\n\"_\tc #0C29CB\",\n\":\tc #081ED1\",\n\"<\tc #4C4CC8\",\n\"[\tc #1F50B1\",\n\"}\tc #0716D4\",\n\"|\tc #3F873F\",\n\"1\tc #0304DA\",\n\"2\tc #0B21D0\",\n\"3\tc #0410D8\",\n\"4\tc #081DD3\",\n\"5\tc #2354AF\",\n\"6\tc #0C21CB\",\n\"7\tc #6868CF\",\n\"8\tc #5353C9\",\n\"9\tc #6968CF\",\n\"0\tc #6363CD\",\n\"a\tc #1E41B2\",\n\"b\tc #A5A5A5\",\n\"c\tc #B8B8B8\",\n\"d\tc #C8C8C8\",\n\"e\tc #B4B4B4\",\n\"f\tc #868686\",\n\"g\tc #848484\",\n\"h\tc #939393\",\n\"i\tc #A6A6A6\",\n\"j\tc #3838BE\",\n\"k\tc #9E9E9E\",\n\"l\tc #6060CC\",\n\"m\tc #B5B5B5\",\n\"n\tc #6B6AD0\",\n\"o\tc #D0D0D0\",\n\"p\tc #CDCDCD\",\n\"q\tc #C4C4C4\",\n\"r\tc #BABABA\",\n\"s\tc #0C1DCB\",\n\"t\tc #487C8A\",\n\"u\tc #B1B1B1\",\n\"v\tc #919191\",\n\"w\tc #6666CE\",\n\"                      \",\n\"                      \",\n\"   .+++++@@##@@++$%   \",\n\"  & *============* $  \",\n\"  -;>            ,')  \",\n\"  -!~            {].  \",\n\"  ^ >            { -  \",\n\"  $ /            { #  \",\n\"  - (            > ^  \",\n\"  ^ _            : <  \",\n\"  + [            } &  \",\n\"  +  ||1{122234>5  .  \",\n\"  + 6#@77..8<^9^0a +  \",\n\"  + &bbbcddddddde@ +  \",\n\"  + ^f^^g       h@ +  \",\n\"  + #i&jkiiiiiiikl -  \",\n\"  @ )m^^ mmmmmmm n ^  \",\n\"  - $o&%pqqqqqqqr7s^  \",\n\"  ^t-uuuh       v<.   \",\n\"   ..)#)..l^$$##w$    \",\n\"                      \",\n\"                      \"};\n/* XPM */\nstatic const char *const stop_s_xpm[] = {\n\"22 22 77 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #703434\",\n\"@      c #E57F7F\",\n\"#      c #E68080\",\n\"$      c #6F2929\",\n\"%      c #DB4343\",\n\"&      c #D31A1A\",\n\"*      c #CE0000\",\n\"=      c #DC4343\",\n\"-      c #D51A1A\",\n\";      c #D00000\",\n\">      c #702929\",\n\",      c #DF4343\",\n\"'      c #D71A1A\",\n\")      c #D30000\",\n\"!      c #6F1B1B\",\n\"~      c #E14343\",\n\"{      c #DA1A1A\",\n\"]      c #D60000\",\n\"^      c #A66666\",\n\"/      c #841717\",\n\"(      c #DA0404\",\n\"_      c #DD1A1A\",\n\":      c #D90000\",\n\"<      c #D56B6B\",\n\"[      c #FFFFFF\",\n\"}      c #E7D5D5\",\n\"|      c #861717\",\n\"1      c #CC0000\",\n\"2      c #DC0000\",\n\"3      c #E64C4C\",\n\"4      c #FADEDE\",\n\"5      c #EAD5D5\",\n\"6      c #722C2C\",\n\"7      c #CF0000\",\n\"8      c #E00000\",\n\"9      c #EB5C5C\",\n\"0      c #FBDFDF\",\n\"a      c #FDF8F8\",\n\"b      c #E40000\",\n\"c      c #DE3535\",\n\"d      c #FEF8F8\",\n\"e      c #E70000\",\n\"f      c #971C1C\",\n\"g      c #EED5D5\",\n\"h      c #FEFAFA\",\n\"i      c #EB0000\",\n\"j      c #AA1C1C\",\n\"k      c #EFD5D5\",\n\"l      c #FDE3E3\",\n\"m      c #F69595\",\n\"n      c #C50000\",\n\"o      c #D50000\",\n\"p      c #EF0000\",\n\"q      c #F77777\",\n\"r      c #FDE4E4\",\n\"s      c #F56565\",\n\"t      c #4D0000\",\n\"u      c #9E0000\",\n\"v      c #D70000\",\n\"w      c #F20000\",\n\"x      c #FAA7A7\",\n\"y      c #F76969\",\n\"z      c #420000\",\n\"A      c #A00000\",\n\"B      c #DA0000\",\n\"C      c #F50000\",\n\"D      c #A20000\",\n\"E      c #DD0000\",\n\"F      c #F80000\",\n\"G      c #430000\",\n\"H      c #A40000\",\n\"I      c #DF0000\",\n\"J      c #FB0000\",\n\"K      c #380000\",\n\"L      c #570000\",\n\"                      \",\n\"                      \",\n\"      .........       \",\n\"     .+@#####@+.      \",\n\"    .$%&*****&%$.     \",\n\"   .$=-;;;;;;;-=$.    \",\n\"  .>,')))))))))',>.   \",\n\" .!~{]]^/]]]/^]]{~!.  \",\n\" .(_::<[}|:|}[<::_(.  \",\n\" .122234[565[432221.  \",\n\" .7888890[a[0988887.  \",\n\" .)bbbbbcd[dcbbbbb).  \",\n\" .]eeeefg[h[gfeeee].  \",\n\" .:iiijk[lml[kjiii:.  \",\n\" .noppq[rspsr[qppon.  \",\n\" .tuvwwxywwwyxwwvut.  \",\n\"  .zABCCCCCCCCCBAz.   \",\n\"   .zDEFFFFFFFEDz.    \",\n\"    .GHIJJJJJIHG.     \",\n\"     .KLLLLLLLK.      \",\n\"      .........       \",\n\"                      \"};\n/* XPM */\nstatic const char *const bm_s_xpm[] = {\n\"20 20 285 2\",\n\"  \tc None\",\n\". \tc #E1DDE3\",\n\"+ \tc #CCCCD5\",\n\"@ \tc #BFC4CB\",\n\"# \tc #BAC1CB\",\n\"$ \tc #C1C9D1\",\n\"% \tc #C5CBD5\",\n\"& \tc #C5CCD6\",\n\"* \tc #C2CBD6\",\n\"= \tc #B9C7D1\",\n\"- \tc #B8C7D2\",\n\"; \tc #B5C6D0\",\n\"> \tc #B4C5CF\",\n\", \tc #B8C7D0\",\n\"' \tc #B4C1CC\",\n\") \tc #ABB8C3\",\n\"! \tc #96A1AC\",\n\"~ \tc #C5C2CA\",\n\"{ \tc #BBBCC8\",\n\"] \tc #D9E0E5\",\n\"^ \tc #E2EEF1\",\n\"/ \tc #E0EAF1\",\n\"( \tc #DEE8F1\",\n\"_ \tc #DAE7F1\",\n\": \tc #D7E6F0\",\n\"< \tc #D5E7F1\",\n\"[ \tc #D0E5F2\",\n\"} \tc #CCE3F4\",\n\"| \tc #CDE7F5\",\n\"1 \tc #CBE2F3\",\n\"2 \tc #CFE2F3\",\n\"3 \tc #DAE7F3\",\n\"4 \tc #798FA9\",\n\"5 \tc #B5B4BF\",\n\"6 \tc #AEAFBB\",\n\"7 \tc #959BAB\",\n\"8 \tc #95A4B7\",\n\"9 \tc #98A8BF\",\n\"0 \tc #97A7BE\",\n\"a \tc #8E9FBC\",\n\"b \tc #7C93BA\",\n\"c \tc #7492BC\",\n\"d \tc #618DBF\",\n\"e \tc #4E8CC5\",\n\"f \tc #268CD6\",\n\"g \tc #3089CD\",\n\"h \tc #4A88C1\",\n\"i \tc #C7D5E7\",\n\"j \tc #748AAE\",\n\"k \tc #B4B2BE\",\n\"l \tc #BDBECA\",\n\"m \tc #A2A9B7\",\n\"n \tc #ADBCCF\",\n\"o \tc #B0C2D7\",\n\"p \tc #A9BCD2\",\n\"q \tc #A8B9D0\",\n\"r \tc #A1B3CB\",\n\"s \tc #9BB0CA\",\n\"t \tc #91AAC7\",\n\"u \tc #86A6C8\",\n\"v \tc #82A4C7\",\n\"w \tc #7B9DC4\",\n\"x \tc #6B96C7\",\n\"y \tc #CEDFF5\",\n\"z \tc #758EB4\",\n\"A \tc #AEACB8\",\n\"B \tc #B6B7C2\",\n\"C \tc #9CA5B3\",\n\"D \tc #A8BACF\",\n\"E \tc #A9BFD7\",\n\"F \tc #A2B6CE\",\n\"G \tc #A1B4CB\",\n\"H \tc #9BAEC5\",\n\"I \tc #94ACC5\",\n\"J \tc #8BA9C7\",\n\"K \tc #7BA1CA\",\n\"L \tc #7FA3CB\",\n\"M \tc #779DC9\",\n\"N \tc #6292CC\",\n\"O \tc #CBDBF5\",\n\"P \tc #7C92B5\",\n\"Q \tc #A2A1AD\",\n\"R \tc #AEB1B8\",\n\"S \tc #949FAC\",\n\"T \tc #A0B4C8\",\n\"U \tc #A6BCD2\",\n\"V \tc #A0B4CB\",\n\"W \tc #A9BCD0\",\n\"X \tc #B1C3D7\",\n\"Y \tc #9EB4CE\",\n\"Z \tc #7C9EC4\",\n\"` \tc #86A8CB\",\n\" .\tc #6B98CB\",\n\"..\tc #759BC9\",\n\"+.\tc #6B93CB\",\n\"@.\tc #CBDBF7\",\n\"#.\tc #5E7BB8\",\n\"$.\tc #A09FA9\",\n\"%.\tc #ACAEB4\",\n\"&.\tc #8C9BA6\",\n\"*.\tc #96ABBC\",\n\"=.\tc #A2B6CB\",\n\"-.\tc #9DB0C5\",\n\";.\tc #B0C0D0\",\n\">.\tc #C9D9E4\",\n\",.\tc #B9CAD9\",\n\"'.\tc #86A1BC\",\n\").\tc #7B9FC5\",\n\"!.\tc #789CC8\",\n\"~.\tc #7B9CC8\",\n\"{.\tc #6190CD\",\n\"].\tc #D0E0FA\",\n\"^.\tc #5A79B9\",\n\"/.\tc #9D9AA5\",\n\"(.\tc #A5A6AA\",\n\"_.\tc #86969F\",\n\":.\tc #9FB4C3\",\n\"<.\tc #A2B8CB\",\n\"[.\tc #8EA4B8\",\n\"}.\tc #B4C4D2\",\n\"|.\tc #DBEAF3\",\n\"1.\tc #C2D4E2\",\n\"2.\tc #7598B7\",\n\"3.\tc #6F99C1\",\n\"4.\tc #7098C6\",\n\"5.\tc #6D92C4\",\n\"6.\tc #688CC9\",\n\"7.\tc #CCD9F6\",\n\"8.\tc #5B78B8\",\n\"9.\tc #969297\",\n\"0.\tc #9D9B9D\",\n\"a.\tc #818B9C\",\n\"b.\tc #ABBED1\",\n\"c.\tc #D8ECF8\",\n\"d.\tc #E9FAFF\",\n\"e.\tc #EBF9FE\",\n\"f.\tc #ECF9FF\",\n\"g.\tc #EDFBFE\",\n\"h.\tc #EBFEFE\",\n\"i.\tc #DBF2FB\",\n\"j.\tc #B2CBE3\",\n\"k.\tc #6992C3\",\n\"l.\tc #5587CF\",\n\"m.\tc #D1E1F8\",\n\"n.\tc #6E85B8\",\n\"o.\tc #958E8B\",\n\"p.\tc #999698\",\n\"q.\tc #75819E\",\n\"r.\tc #8FA2BC\",\n\"s.\tc #ABBDCE\",\n\"t.\tc #C5D7E1\",\n\"u.\tc #E3F1F8\",\n\"v.\tc #EEFAFF\",\n\"w.\tc #E6F5FB\",\n\"x.\tc #D1E3ED\",\n\"y.\tc #A3BAD0\",\n\"z.\tc #6C97C6\",\n\"A.\tc #7195C4\",\n\"B.\tc #5186CD\",\n\"C.\tc #CEDDF2\",\n\"D.\tc #637CBA\",\n\"E.\tc #90847C\",\n\"F.\tc #918E91\",\n\"G.\tc #6C7A9E\",\n\"H.\tc #8D9FB9\",\n\"I.\tc #809CB9\",\n\"J.\tc #B7CBDE\",\n\"K.\tc #EBF9FF\",\n\"L.\tc #F1FFFF\",\n\"M.\tc #EFFDFF\",\n\"N.\tc #C9E0F1\",\n\"O.\tc #2987D1\",\n\"P.\tc #6692C2\",\n\"Q.\tc #5889CB\",\n\"R.\tc #D2DEF5\",\n\"S.\tc #5474B6\",\n\"T.\tc #8A7673\",\n\"U.\tc #86848C\",\n\"V.\tc #6275A0\",\n\"W.\tc #7B94B4\",\n\"X.\tc #879EBA\",\n\"Y.\tc #C5D7E5\",\n\"Z.\tc #E1F1FA\",\n\"`.\tc #CCDAE7\",\n\" +\tc #D4E4EE\",\n\".+\tc #D4E9F4\",\n\"++\tc #85A5CA\",\n\"@+\tc #6794C4\",\n\"#+\tc #5B8BC6\",\n\"$+\tc #5086CB\",\n\"%+\tc #CDD9F0\",\n\"&+\tc #6F84B2\",\n\"*+\tc #837377\",\n\"=+\tc #7D7989\",\n\"-+\tc #536AA5\",\n\";+\tc #6E8AB5\",\n\">+\tc #9AB0CD\",\n\",+\tc #B7CBDD\",\n\"'+\tc #AABFD5\",\n\")+\tc #7C9DC1\",\n\"!+\tc #8CAACB\",\n\"~+\tc #B0C6DA\",\n\"{+\tc #9BB5D6\",\n\"]+\tc #4B89CD\",\n\"^+\tc #5486CA\",\n\"/+\tc #638EC7\",\n\"(+\tc #C5D4EC\",\n\"_+\tc #6D83B2\",\n\":+\tc #82707F\",\n\"<+\tc #747289\",\n\"[+\tc #4A66A8\",\n\"}+\tc #7792BC\",\n\"|+\tc #8AA7CC\",\n\"1+\tc #85A3CD\",\n\"2+\tc #7A9BC4\",\n\"3+\tc #6992C1\",\n\"4+\tc #6693C6\",\n\"5+\tc #769ECF\",\n\"6+\tc #6A9BDA\",\n\"7+\tc #6091CF\",\n\"8+\tc #4C84D1\",\n\"9+\tc #4E72D7\",\n\"0+\tc #CFDFF6\",\n\"a+\tc #3F67CB\",\n\"b+\tc #7A6A7D\",\n\"c+\tc #6B6A89\",\n\"d+\tc #3E5FAD\",\n\"e+\tc #7A93BF\",\n\"f+\tc #7B9BC9\",\n\"g+\tc #638FC8\",\n\"h+\tc #6C92C7\",\n\"i+\tc #6F96C9\",\n\"j+\tc #6292CD\",\n\"k+\tc #5890D1\",\n\"l+\tc #4D8AD5\",\n\"m+\tc #598DD2\",\n\"n+\tc #4982D2\",\n\"o+\tc #4978DA\",\n\"p+\tc #D6E4F8\",\n\"q+\tc #486AC5\",\n\"r+\tc #5E3782\",\n\"s+\tc #63628C\",\n\"t+\tc #395CAF\",\n\"u+\tc #748EBD\",\n\"v+\tc #7E9BC5\",\n\"w+\tc #7093C5\",\n\"x+\tc #7093C4\",\n\"y+\tc #6B92C6\",\n\"z+\tc #608FCA\",\n\"A+\tc #5A8ECD\",\n\"B+\tc #5A8BCD\",\n\"C+\tc #5586CD\",\n\"D+\tc #6289C9\",\n\"E+\tc #4E7DC9\",\n\"F+\tc #A6B4CF\",\n\"G+\tc #687CB2\",\n\"H+\tc #948BA3\",\n\"I+\tc #63689A\",\n\"J+\tc #4566AD\",\n\"K+\tc #6F86B4\",\n\"L+\tc #7A91B9\",\n\"M+\tc #768FB9\",\n\"N+\tc #738FB9\",\n\"O+\tc #6D8DBB\",\n\"P+\tc #6489BE\",\n\"Q+\tc #5F87C0\",\n\"R+\tc #5D86C1\",\n\"S+\tc #5D83C0\",\n\"T+\tc #6D86B9\",\n\"U+\tc #647EB4\",\n\"V+\tc #6A7BAA\",\n\"W+\tc #6F80AD\",\n\"X+\tc #6975A1\",\n\"Y+\tc #4C6DAC\",\n\"Z+\tc #6C81AF\",\n\"`+\tc #7085B0\",\n\" @\tc #7187B1\",\n\".@\tc #7089B2\",\n\"+@\tc #6D88B3\",\n\"@@\tc #6485B5\",\n\"#@\tc #6183B8\",\n\"$@\tc #5F82B9\",\n\"%@\tc #5B7DB8\",\n\"&@\tc #6882B2\",\n\"*@\tc #7586AD\",\n\"                                        \",\n\"                                        \",\n\"    . + @ # $ % & * = - ; > , ' ) !     \",\n\"    ~ { ] ^ / ( _ : < [ } | 1 2 3 4     \",\n\"    5 6 7 8 9 0 a b c d e f g h i j     \",\n\"    k l m n o p q r s t u v w x y z     \",\n\"    A B C D E F G H I J K L M N O P     \",\n\"    Q R S T U V W X Y Z `  ...+.@.#.    \",\n\"    $.%.&.*.=.-.;.>.,.'.).!.~.{.].^.    \",\n\"    /.(._.:.<.[.}.|.1.2.3.4.5.6.7.8.    \",\n\"    9.0.a.b.c.d.e.f.g.h.i.j.k.l.m.n.    \",\n\"    o.p.q.r.s.t.u.v.w.x.y.z.A.B.C.D.    \",\n\"    E.F.G.H.I.J.K.L.M.N.O.P.k.Q.R.S.    \",\n\"    T.U.V.W.X.Y.Z.`. +.+++@+#+$+%+&+    \",\n\"    *+=+-+;+>+,+'+)+!+~+{+]+^+/+(+_+    \",\n\"    :+<+[+}+|+1+2+3+4+5+6+7+8+9+0+a+    \",\n\"    b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+    \",\n\"    r+s+t+u+v+w+x+y+z+A+B+C+D+E+F+G+    \",\n\"    H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+    \",\n\"      X+Y+Z+`+ @.@+@@@#@$@%@&@*@        \"};\n/* XPM */\nstatic const char *const tools_s_xpm[] = {\n\"22 22 75 1\",\n\" \tc None\",\n\".\tc #5877CB\",\n\"+\tc #5A79CA\",\n\"@\tc #5978CA\",\n\"#\tc #5A79CC\",\n\"$\tc #637FC8\",\n\"%\tc #5D7CCB\",\n\"&\tc #5876CB\",\n\"*\tc #5D7BC9\",\n\"=\tc #6482D0\",\n\"-\tc #647FC6\",\n\";\tc #A2A6CC\",\n\">\tc #617FC8\",\n\",\tc #5A7ACC\",\n\"'\tc #8094CA\",\n\")\tc #5978CB\",\n\"!\tc #5F7CC9\",\n\"~\tc #607DC8\",\n\"{\tc #5A79CB\",\n\"]\tc #5B7ACB\",\n\"^\tc #5C7BCA\",\n\"/\tc #5274CC\",\n\"(\tc #879BCC\",\n\"_\tc #5A7BCF\",\n\":\tc #6782C9\",\n\"<\tc #6480C8\",\n\"[\tc #5B7BCB\",\n\"}\tc #6280CC\",\n\"|\tc #6681C6\",\n\"1\tc #7D94CC\",\n\"2\tc #5777CD\",\n\"3\tc #5676CB\",\n\"4\tc #5375CC\",\n\"5\tc #92A3D2\",\n\"6\tc #5777CC\",\n\"7\tc #6683CC\",\n\"8\tc #6883C9\",\n\"9\tc #B8C6E9\",\n\"0\tc #7B91CC\",\n\"a\tc #6883C7\",\n\"b\tc #8397CC\",\n\"c\tc #B7C5E6\",\n\"d\tc #758CC7\",\n\"e\tc #8594CC\",\n\"f\tc #617EC7\",\n\"g\tc #778DC8\",\n\"h\tc #5676CC\",\n\"i\tc #90A2D0\",\n\"j\tc #7189C6\",\n\"k\tc #5F7BC9\",\n\"l\tc #446BD2\",\n\"m\tc #96A8D3\",\n\"n\tc #6F88C6\",\n\"o\tc #607CC8\",\n\"p\tc #7C94CD\",\n\"q\tc #7990CC\",\n\"r\tc #758ECC\",\n\"s\tc #758DCC\",\n\"t\tc #6D85C6\",\n\"u\tc #6580C6\",\n\"v\tc #7C92CA\",\n\"w\tc #7F96D2\",\n\"x\tc #6983C6\",\n\"y\tc #6882C6\",\n\"z\tc #8297CE\",\n\"A\tc #6E89CC\",\n\"B\tc #7189C5\",\n\"C\tc #8699CA\",\n\"D\tc #758BC6\",\n\"E\tc #6481CA\",\n\"F\tc #6482CC\",\n\"G\tc #708ACC\",\n\"H\tc #748DCD\",\n\"I\tc #5D7BCC\",\n\"J\tc #5C7ED3\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"         .++@         \",\n\"         #$%&*        \",\n\"         =-;;>@       \",\n\"          ,';;;@      \",\n\"    @     )!~;;;+     \",\n\"   @@{     @+;;;]     \",\n\"   @^;)     /(;;#_    \",\n\"   @:;;+    <;;;[}    \",\n\"   @|;;123^45;;;67    \",\n\"    8;;;90abc;;;de    \",\n\"    ^^f;;;;;;;;;g     \",\n\"     ~hi;;;;;;;;;j    \",\n\"      klm;;;;;;;;no   \",\n\"       ^hpqrstu;;;;v  \",\n\"             wxy;;;z  \",\n\"              A~BCDE  \",\n\"               FGHIJ  \",\n\"                      \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const  newtab_s_xpm[] = {\n\"22 22 75 1\",\n\" \tc None\",\n\"B\tc #5877CB\",\n\"C\tc #5A79CA\",\n\"D\tc #5978CA\",\n\"E\tc #5A79CC\",\n\"F\tc #637FC8\",\n\"G\tc #5D7CCB\",\n\"H\tc #5876CB\",\n\"I\tc #5D7BC9\",\n\"J\tc #6482D0\",\n\"K\tc #647FC6\",\n\"L\tc #A2A6CC\",\n\"M\tc #617FC8\",\n\"N\tc #5A7ACC\",\n\"O\tc #8094CA\",\n\"P\tc #5978CB\",\n\"Q\tc #5F7CC9\",\n\"R\tc #607DC8\",\n\"S\tc #5A79CB\",\n\"T\tc #5B7ACB\",\n\"U\tc #5C7BCA\",\n\"V\tc #5274CC\",\n\"W\tc #879BCC\",\n\"X\tc #5A7BCF\",\n\"Y\tc #6782C9\",\n\"Z\tc #6480C8\",\n\"a\tc #5B7BCB\",\n\"b\tc #6280CC\",\n\"c\tc #6681C6\",\n\"d\tc #7D94CC\",\n\"e\tc #5777CD\",\n\"f\tc #5676CB\",\n\"g\tc #5375CC\",\n\"h\tc #92A3D2\",\n\"i\tc #5777CC\",\n\"j\tc #6683CC\",\n\"k\tc #6883C9\",\n\"l\tc #B8C6E9\",\n\"m\tc #7B91CC\",\n\"n\tc #6883C7\",\n\"o\tc #8397CC\",\n\"p\tc #B7C5E6\",\n\"q\tc #758CC7\",\n\"r\tc #8594CC\",\n\"s\tc #617EC7\",\n\"t\tc #778DC8\",\n\"u\tc #5676CC\",\n\"v\tc #90A2D0\",\n\"w\tc #7189C6\",\n\"x\tc #5F7BC9\",\n\"y\tc #446BD2\",\n\"z\tc #96A8D3\",\n\"0\tc #6F88C6\",\n\"1\tc #607CC8\",\n\"2\tc #7C94CD\",\n\"3\tc #7990CC\",\n\"4\tc #758ECC\",\n\"5\tc #758DCC\",\n\"6\tc #6D85C6\",\n\"7\tc #6580C6\",\n\"8\tc #7C92CA\",\n\"9\tc #7F96D2\",\n\"+\tc #6983C6\",\n\"/\tc #6882C6\",\n\"!\tc #8297CE\",\n\"#\tc #6E89CC\",\n\"$\tc #7189C5\",\n\"%\tc #8699CA\",\n\"&\tc #758BC6\",\n\"'\tc #6481CA\",\n\"(\tc #6482CC\",\n\")\tc #708ACC\",\n\"*\tc #748DCD\",\n\",\tc #5D7BCC\",\n\"-\tc #5C7ED3\",\n\"                      \",\n\"        BBBBBB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\" BBBBBBBBLLLLBBBBBBBB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BLLLLLLLLLLLLLLLLLLB \",\n\" BBBBBBBBLLLLBBBBBBBB \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BLLLLB        \",\n\"        BBBBBB        \",\n\"                      \"\n};\n\n/* XPM */\nstatic const char *const new_s_xpm[] = {\n\"11 11 35 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #482929\",\n\"@      c #0F0808\",\n\"#      c #390606\",\n\"$      c #A04242\",\n\"%      c #925050\",\n\"&      c #0F0707\",\n\"*      c #0B0000\",\n\"=      c #500000\",\n\"-      c #8C0101\",\n\";      c #944444\",\n\">      c #221515\",\n\",      c #0A0000\",\n\"'      c #560000\",\n\")      c #A10909\",\n\"!      c #AE3636\",\n\"~      c #230000\",\n\"{      c #AE0000\",\n\"]      c #B40000\",\n\"^      c #180808\",\n\"/      c #B43535\",\n\"(      c #C40000\",\n\"_      c #960000\",\n\":      c #190606\",\n\"<      c #C02B2B\",\n\"[      c #E00000\",\n\"}      c #6B0000\",\n\"|      c #120000\",\n\"1      c #590000\",\n\"2      c #A30000\",\n\"3      c #680000\",\n\"4      c #0F0000\",\n\"5      c #350000\",\n\"6      c #100000\",\n\"  .     .  \",\n\" .+@   @+. \",\n\".#$%& &%$#.\",\n\" *=-;>;-=* \",\n\"  ,')!)',  \",\n\"   ~{]{~   \",\n\"  ^/(_(/^  \",\n\" :<[}|}[<: \",\n\".1234 4321.\",\n\" .56   65. \",\n\"  .     .  \"};\n\n/* XPM */\nstatic const char *const search_xpm[] = {\n\"14 16 14 1\",\n\" \tc None\",\n\".\tc #3838B0\",\n\"+\tc #3939B1\",\n\"@\tc #3939B3\",\n\"#\tc #EEEEEE\",\n\"$\tc #3939B2\",\n\"%\tc #3838B1\",\n\"&\tc #140033\",\n\"*\tc #1B0044\",\n\"=\tc #360088\",\n\"-\tc #290066\",\n\";\tc #4400AA\",\n\">\tc #4A00BB\",\n\",\tc #5100CC\",\n\"   ...++      \",\n\"  .     @     \",\n\" +  #    +    \",\n\"$  ###    $   \",\n\"+   #     %   \",\n\"%         .   \",\n\"%         .   \",\n\" @       .    \",\n\"  $     .     \",\n\"   +$$&*&     \",\n\"       *=*    \",\n\"       -;-    \",\n\"        =>=   \",\n\"        =,=   \",\n\"         =,=  \",\n\"         ;;   \"};\n\n/* XPM */\nstatic const char *const help_xpm[] = {\n\"14 16 7 1\",\n\" \tc None\",\n\".\tc #3766A4\",\n\"+\tc #5C8BC9\",\n\"@\tc #91B1DA\",\n\"#\tc #92B2DB\",\n\"$\tc #5385C6\",\n\"%\tc #325E96\",\n\"              \",\n\"    ...+++    \",\n\"   .   .+@.   \",\n\"  ..    .#@+  \",\n\"  ....  .@#.  \",\n\"  ....  .#$.  \",\n\"   ..  .#$.   \",\n\"       .$.    \",\n\"      .$.     \",\n\"      ..      \",\n\"      ..      \",\n\"              \",\n\"      ..      \",\n\"     .$%.     \",\n\"     .%$.     \",\n\"      ..      \"};\n\n/* XPM */\nstatic const char *const mini_bug_xpm[] = {\n\"16 16 7 1\",\n\"       c None\",\n\".      c #000000000000\",\n\"r      c #FFFF00000000\",\n\"X      c #BEFBC30BBEFB\",\n\"o      c #861782078617\",\n\"O      c #FFFFFFFFFFFF\",\n\"+      c #30C230C230C2\",\n\"                \",\n\"     .   .      \",\n\"      ...       \",\n\"    X.....X     \",\n\"    o.O...o     \",\n\"   o.O...o.o    \",\n\"   .rrrXrrr.    \",\n\"  .rrrrXrrrr.   \",\n\"   .rrrXrrr.    \",\n\"  .o.rrXrr.o.   \",\n\"    .rrXrr.     \",\n\"   .XrrXrrX.    \",\n\"      .o.       \",\n\"           .+++.\",\n\"            .o. \",\n\"             .  \"};\n\n/* XPM */\nstatic const char *const mini_ok_xpm[] = {\n\"15 15 5 1\",\n\"@ c #000000\",\n\"a c #808080\",\n\"b c #303030\",\n\"c c #606060\",\n\"  s none m none c none\",\n\"               \",\n\"               \",\n\"            @  \",\n\"           @@  \",\n\"          @@@  \",\n\"         @@@   \",\n\"   @@   @@@    \",\n\"  @@@  @@@     \",\n\"  @@@ @@@      \",\n\"  @@@@@@       \",\n\"  @@@@@        \",\n\"  @@@@         \",\n\"   @@     @bbb@\",\n\"           @a@ \",\n\"            @  \"\n};\n\n#if 0\n\n/* XPM */\nstatic const char *const left_i_xpm[] = {\n\"22 22 3 1\",\n\"  c None\",\n\". c #000000\",\n\"@ c gray70\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"        @@            \",\n\"       @@@            \",\n\"      @@@@            \",\n\"     @@@@@@@@@@ @@@@  \",\n\"    @@@@@@@@@@@ @@@@  \",\n\"   @@@@@@@@@@@@ @@@@  \",\n\"  @@@@@@@@@@@@@ @@@@  \",\n\"  @@@@@@@@@@@@@ @@@@  \",\n\"   @@@@@@@@@@@@ @@@@  \",\n\"    @@@@@@@@@@@ @@@@  \",\n\"     @@@@@@@@@@ @@@@  \",\n\"      @@@@            \",\n\"       @@@            \",\n\"        @@            \",\n\"                 @@@@@\",\n\"                  @ @ \",\n\"                   @  \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const right_i_xpm[] = {\n\"22 22 3 1\",\n\"  c None\",\n\". c #000000\",\n\"@ c gray70\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"            @@        \",\n\"            @@@       \",\n\"            @@@@      \",\n\"  @@@@ @@@@@@@@@@     \",\n\"  @@@@ @@@@@@@@@@@    \",\n\"  @@@@ @@@@@@@@@@@@   \",\n\"  @@@@ @@@@@@@@@@@@@  \",\n\"  @@@@ @@@@@@@@@@@@@  \",\n\"  @@@@ @@@@@@@@@@@@   \",\n\"  @@@@ @@@@@@@@@@@    \",\n\"  @@@@ @@@@@@@@@@     \",\n\"            @@@@      \",\n\"            @@@       \",\n\"            @@        \",\n\"                 @@@@@\",\n\"                  @ @ \",\n\"                   @  \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const stop_i_xpm[] = {\n/* columns rows colors chars-per-pixel */\n\"22 22 2 1\",\n\"       c None\",\n\"@      c gray70\",\n/* pixels */\n\"                      \",\n\"                      \",\n\"      @@@@@@@@@       \",\n\"     @@@@@@@@@@@      \",\n\"    @@@@@@@@@@@@@     \",\n\"   @@@@@@@@@@@@@@@    \",\n\"  @@@@@@@@@@@@@@@@@   \",\n\" @@@@@@@@@@@@@@@@@@@  \",\n\" @@@@@@  @@@  @@@@@@  \",\n\" @@@@@@   @   @@@@@@  \",\n\" @@@@@@@     @@@@@@@  \",\n\" @@@@@@@@   @@@@@@@@  \",\n\" @@@@@@@     @@@@@@@  \",\n\" @@@@@@   @   @@@@@@  \",\n\" @@@@@@  @@@  @@@@@@  \",\n\" @@@@@@@@@@@@@@@@@@@  \",\n\"  @@@@@@@@@@@@@@@@@   \",\n\"   @@@@@@@@@@@@@@@    \",\n\"    @@@@@@@@@@@@@     \",\n\"     @@@@@@@@@@@      \",\n\"      @@@@@@@@@       \",\n\"                      \"\n};\n\n/* XPM */\nstatic const char *const stop_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"    @@@@@@@@    \",\n\"   @@@@@@@@@@   \",\n\"  @@@@@@@@@@@@  \",\n\" @@@@@@@@@@@@@@ \",\n\"@@@@@@@@@@@@@@@@\",\n\"@@@@@ @@@@ @@@@@\",\n\"@@@@@@ @@ @@@@@@\",\n\"@@@@@@@  @@@@@@@\",\n\"@@@@@@@  @@@@@@@\",\n\"@@@@@@ @@ @@@@@@\",\n\"@@@@@ @@@@ @@@@@\",\n\"@@@@@@@@@@@@@@@@\",\n\" @@@@@@@@@@@@@@ \",\n\"  @@@@@@@@@@@@  \",\n\"   @@@@@@@@@@   \",\n\"    @@@@@@@@    \"};\n\n/* XPM */\nstatic const char *const left_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"                \",\n\"      @@        \",\n\"     @@@        \",\n\"    @@@@        \",\n\"   @@@@@@@@ @@@@\",\n\"  @@@@@@@@@ @@@@\",\n\" @@@@@@@@@@ @@@@\",\n\"@@@@@@@@@@@ @@@@\",\n\"@@@@@@@@@@@ @@@@\",\n\" @@@@@@@@@@ @@@@\",\n\"  @@@@@@@@@ @@@@\",\n\"   @@@@@@@@ @@@@\",\n\"    @@@@        \",\n\"     @@@   @@@@@\",\n\"      @@    @ @ \",\n\"             @  \"};\n\n/* XPM */\nstatic const char *const right_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"                \",\n\"        @@      \",\n\"        @@@     \",\n\"        @@@@    \",\n\"@@@@ @@@@@@@@   \",\n\"@@@@ @@@@@@@@@  \",\n\"@@@@ @@@@@@@@@@ \",\n\"@@@@ @@@@@@@@@@@\",\n\"@@@@ @@@@@@@@@@@\",\n\"@@@@ @@@@@@@@@@ \",\n\"@@@@ @@@@@@@@@  \",\n\"@@@@ @@@@@@@@   \",\n\"        @@@@    \",\n\"        @@@@@@@@\",\n\"        @@  @ @ \",\n\"             @  \"};\n#endif\n\n#endif /* __PIXMAPS_H__ */\n"
  },
  {
    "path": "src/pixmaps.h",
    "content": "/*\n * File: pixmaps.h\n *\n * Copyright (C) 2000, 2001 Jorge Arellano Cid <jcid@dillo.org>\n * Design by John Grantham, Dipl.-Designer; http://www.grantham.de/\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef __PIXMAPS_H__\n#define __PIXMAPS_H__\n\n/* XPM */\nstatic const char *const left_xpm[] = {\n\"22 22 8 1 \",\n\"  c #282C31\",\n\". c #292C31\",\n\"X c #282D31\",\n\"o c #292D31\",\n\"O c #282C32\",\n\"+ c #FF0000\",\n\"@ c #292D32\",\n\"# c None\",\n/* pixels */\n\"#########@@@##########\",\n\"########@@@###########\",\n\"#######@@@############\",\n\"######@@@#############\",\n\"#####@@@##############\",\n\"####@@@###############\",\n\"###@@@################\",\n\"##@@@#################\",\n\"#@@@##################\",\n\"@@@###################\",\n\"@@@@@@@@@@@@@@@@@@@@@@\",\n\"@@@@@@@@@@@@@@@@@@@@@@\",\n\"@@@###################\",\n\"#@@@##################\",\n\"##@@@#################\",\n\"###@@@################\",\n\"####@@@###############\",\n\"#####@@@##############\",\n\"######@@@#############\",\n\"#######@@@#######+++++\",\n\"########@@@#######+++#\",\n\"#########@@@#######+##\"\n};\n\n/* XPM */\nstatic const char *const right_xpm[] = {\n\"22 22 10 1\",\n\"  c #272D31\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #FF0000\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c None\",\n/* pixels */\n\"%%%%%%%%%%OOO%%%%%%%%%\",\n\"%%%%%%%%%%%OOO%%%%%%%%\",\n\"%%%%%%%%%%%%OOO%%%%%%%\",\n\"%%%%%%%%%%%%%OOO%%%%%%\",\n\"%%%%%%%%%%%%%%OOO%%%%%\",\n\"%%%%%%%%%%%%%%%OOO%%%%\",\n\"%%%%%%%%%%%%%%%%OOO%%%\",\n\"%%%%%%%%%%%%%%%%%OOO%%\",\n\"%%%%%%%%%%%%%%%%%%OOO%\",\n\"%%%%%%%%%%%%%%%%%%%OOO\",\n\"OOOOOOOOOOOOOOOOOOOOOO\",\n\"OOOOOOOOOOOOOOOOOOOOOO\",\n\"%%%%%%%%%%%%%%%%%%%OOO\",\n\"%%%%%%%%%%%%%%%%%%OOO%\",\n\"%%%%%%%%%%%%%%%%%OOO%%\",\n\"%%%%%%%%%%%%%%%%OOO%%%\",\n\"%%%%%%%%%%%%%%%OOO%%%%\",\n\"%%%%%%%%%%%%%%OOO%%%%%\",\n\"%%%%%%%%%%%%%OOO%%%%%%\",\n\"%%%%%%%%%%%%OOO%%+++++\",\n\"%%%%%%%%%%%OOO%%%%+++%\",\n\"%%%%%%%%%%OOO%%%%%%+%%\"\n};\n/* XPM */\nstatic const char *const reload_xpm[] = {\n\"22 22 10 1\",\n\"  c #282B31\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #282C32\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c None\",\n/* pixels */\n\"%%%%%%%%%%%%%%%%%%%%%%\",\n\"%%%%%%%%@@@@@@@@%%%%%%\",\n\"%%%%%%%@@@@@@@@@@%%%%%\",\n\"%%%%%%@@@%%%%%%@@@%%%@\",\n\"%%%%%@@@%%%%%%%%@@@%@@\",\n\"%%%%@@@%%%%%%%%%%@@@@@\",\n\"%%%@@@%%%%%%%%%%%%@@@@\",\n\"%%@@@%%%%%%%%%%%%@@@@@\",\n\"%@@@%%%%%%%%%%%%@@@@@@\",\n\"@@@%%%%%%%%%%%%%%%%%%%\",\n\"%%%%%%%%%%%%%%%%%%%%%%\",\n\"%%%%%%%%%%%%%%%%%%%%%%\",\n\"%%%%%%%%%%%%%%%%%%%@@@\",\n\"%@@@@@@%%%%%%%%%%%@@@%\",\n\"%@@@@@%%%%%%%%%%%@@@%%\",\n\"%@@@@%%%%%%%%%%%@@@%%%\",\n\"%@@@@@%%%%%%%%%@@@%%%%\",\n\"%@@%@@@%%%%%%%@@@%%%%%\",\n\"%@%%%@@@%%%%%@@@%%%%%%\",\n\"%%%%%%@@@@@@@@@%%%%%%%\",\n\"%%%%%%%@@@@@@@%%%%%%%%\",\n\"%%%%%%%%%%%%%%%%%%%%%%\"\n};\n/* XPM */\nstatic const char *const home_xpm[] = {\n\"22 22 23 1\",\n\"  c #282C2F\",\n\". c #272B30\",\n\"X c #272B32\",\n\"o c #272D30\",\n\"O c #272C32\",\n\"+ c #272D32\",\n\"@ c #292B30\",\n\"# c #282B31\",\n\"$ c #292B31\",\n\"% c #292B32\",\n\"& c #282C30\",\n\"* c #292C30\",\n\"= c #FF0000\",\n\"- c #292D30\",\n\"; c #282C31\",\n\": c #292C31\",\n\"> c #282D31\",\n\", c #292D31\",\n\"< c #282C32\",\n\"1 c #292C32\",\n\"2 c #282D32\",\n\"3 c #292D32\",\n\"4 c None\",\n/* pixels */\n\"44444444-----444444444\",\n\"4444444-------44444444\",\n\"444444--.444-------444\",\n\"44444--%44444---4 o444\",\n\"4444--%4444444----%444\",\n\"444--%444444444----444\",\n\"44--%44444444444----44\",\n\"4--%4444444444444----4\",\n\"---444444444444444----\",\n\"---4444444444444444---\",\n\"4--4444444444444444--4\",\n\"4--4444444444444444--4\",\n\"4--4444444444444444--4\",\n\"4--4444444==4444444--4\",\n\"4--444444====444444--4\",\n\"4--444444====444444--4\",\n\"4--4444444==4444444--4\",\n\"4--4444444444444444--4\",\n\"4--4444444444444444--4\",\n\"4--4444444444444444--4\",\n\"%--------------------%\",\n\"----------------------\"\n};\n/* XPM */\nstatic const char *const save_xpm[] = {\n\"22 22 11 1\",\n\"  c #272C31\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #FF0000\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c #282E32\",\n\"& c None\",\n/* pixels */\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"..&&&&&&&&&&&&&&&&&&..\",\n\"...&&&&&&&&&&&&&&&&...\",\n\"&...&&&&&&&&&&&&&&...&\",\n\"&&...&&&&&&&&&&&&...&&\",\n\"&&&...&&++++++&&...&&&\",\n\"&&&& ..&&++++&&...&&&&\",\n\"&&&&&...&&++&&...&&&&&\",\n\"&&&&&&...&&&&...&&&&&&\",\n\"&&&&&&&...&&...&&&&&&&\",\n\"&&&&&&&&......&&&&&&&&\",\n\"&&&&&&&&&....&&&&&&&&&\",\n\"&&&&&&&&&&..&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&&&&&&&\"\n};\n/* XPM */\nstatic const char *const stop_xpm[] = {\n\"22 22 10 1\",\n\"  c #272D32\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #282C32\",\n\"@ c #FF0000\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c None\",\n/* pixels */\n\"%%%%$$$$$$$$$$$$$$%%%%\",\n\"%%$$$$$$$$$$$$$$$$$$%%\",\n\"%$$$%%%%%%%%%%%%%%$$$%\",\n\"%$$%%%%%%%%%%%%%%%%$$%\",\n\"$$%%%%%%%%%%%%%%%%%%$$\",\n\"$$%%%%%%%%%%%%%%%%%%$$\",\n\"$$%%%%%%%%%%%%%%%%%%$$\",\n\"$$%%%%%%%%%%%%%%%%%%$$\",\n\"$$%%%%%%@@%%@@%%%%%%$$\",\n\"$$%%%%%%@@@@@@%%%%%%$$\",\n\"$$%%%%%%%@@@@%%%%%%%$$\",\n\"$$%%%%%%%@@@@%%%%%%%$$\",\n\"$$%%%%%%@@@@@@%%%%%%$$\",\n\"$$%%%%%%@@%%@@%%%%%%$$\",\n\"$$%%%%%%%%%%%%%%%%%%$$\",\n\"$$%%%%%%%%%%%%%%%%%%$$\",\n\"$$%%%%%%%%%%%%%%%%%%$$\",\n\"$$%%%%%%%%%%%%%%%%%%$$\",\n\"%$$%%%%%%%%%%%%%%%%$$%\",\n\"%$$$%%%%%%%%%%%%%%$$$%\",\n\"%%$$$$$$$$$$$$$$$$$$%%\",\n\"%%%%$$$$$$$$$$$$$$%%%%\"\n};\n/* XPM */\nstatic const char *const bm_xpm[] = {\n\"22 22 9 1\",\n\"  c #282C31\",\n\". c #292C31\",\n\"X c #282D31\",\n\"o c #292D31\",\n\"O c #282C32\",\n\"+ c #FF0000\",\n\"@ c #282D32\",\n\"# c #292D32\",\n\"$ c None\",\n/* pixels */\n\"$                    $\",\n\"                      \",\n\"   $$$$$$$  $$++$$$+  \",\n\"  $$$$$$$$  $$++$$++  \",\n\"  $$$$$$$$  $$++$+++  \",\n\"  $$$$$$$$  $$+++++$  \",\n\"  $$$$$$$$  $$++++$$  \",\n\"  $$$$$$$$  $$+++$$$  \",\n\"  $$$$$$$$  $$++$$$$  \",\n\"  $$$$$$$$  $$+$$$$$  \",\n\"  $$$$$$$$  $$$$$$$$  \",\n\"  $$    $$  $$$$$$$$  \",\n\"  $$    $$  $$$$$$$$  \",\n\"  $$$$$$$$  $$$$$$$$  \",\n\"  $$$$$$$$  $$$$$$$$  \",\n\"  $$    $$  $$$$$$$$  \",\n\"  $$    $$  $$$$$$$$  \",\n\"  $$$$$$$$  $$$$$$$$  \",\n\"  $$$$$$$$  $$$$$$$$  \",\n\"   $$$$$$$  $$$$$$$   \",\n\"                      \",\n\"$                    $\"\n};\n/* XPM */\nstatic const char *const tools_xpm[] = {\n\"22 22 10 1\",\n\"  c #282C31\",\n\". c #292C31\",\n\"X c #282D31\",\n\"o c #292D31\",\n\"O c #282E31\",\n\"+ c #282C32\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c None\",\n/* pixels */\n\"%%%%%%%XXXX%%%%%%%%%%%\",\n\"%%%%%%XXXXXX%%%%%%%%%%\",\n\"%%%%%XXX%%XXX%%%%%%%%%\",\n\"%%%%XXX%%%%XXX%%%%%%%%\",\n\"XXXXXX%%%%%%XXXXXXXXXX\",\n\"%XXXXX%%%%%%XXXXXXXXX%\",\n\"%%%%XXX%%%%XXX%%%%%%%%\",\n\"%%%%%XXX%%XXX%%%%%%%%%\",\n\"%%%%%%XXXXXX%%%%%%%%%%\",\n\"%%%%%%%XXXX%%%%%%%%%%%\",\n\"%%%%%%%%%%%%%%%%%%%%%%\",\n\"%%%%%%%%%%%%%%%%%%%%%%\",\n\"%%%%%%%%%%%XXXX%%%%%%%\",\n\"%%%%%%%%%%XXXXXX%%%%%%\",\n\"%%%%%%%%%XXX%%XXX%%%%%\",\n\"%%%%%%%%XXX%%%%XXX%%%%\",\n\"%XXXXXXXXX%%%%%%XXXXX%\",\n\"XXXXXXXXXX%%%%%%XXXXXX\",\n\"%%%%%%%%XXX%%%%XXX%%%%\",\n\"%%%%%%%%%XXX%%XXX%%%%%\",\n\"%%%%%%%%%%XXXXXX%%%%%%\",\n\"%%%%%%%%%%%XXXX%%%%%%%\"\n};\n/* Small icons here */\n\n/* XPM */\nstatic const char *const left_s_xpm[] = {\n\"16 16 8 1\",\n\"  c #282C31\",\n\". c #292C31\",\n\"X c #282D31\",\n\"o c #292D31\",\n\"O c #282C32\",\n\"+ c #FF0000\",\n\"@ c #292D32\",\n\"# c None\",\n/* pixels */\n\"######@@@#######\",\n\"#####@@@########\",\n\"####@@@#########\",\n\"###@@@##########\",\n\"##@@@###########\",\n\"#@@@############\",\n\"@@@#############\",\n\"@@@@@@@@@@@@@@@@\",\n\"@@@@@@@@@@@@@@@@\",\n\"@@@#############\",\n\"#@@@############\",\n\"##@@@###########\",\n\"###@@@##########\",\n\"####@@@#########\",\n\"#####@@@#####+++\",\n\"######@@@#####+#\",\n};\n/* XPM */\nstatic const char *const right_s_xpm[] = {\n\"16 16 10 1\",\n\"  c #272D31\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #FF0000\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c None\",\n/* pixels */\n\"%%%%%%%OOO%%%%%%\",\n\"%%%%%%%%OOO%%%%%\",\n\"%%%%%%%%%OOO%%%%\",\n\"%%%%%%%%%%OOO%%%\",\n\"%%%%%%%%%%%OOO%%\",\n\"%%%%%%%%%%%%OOO%\",\n\"%%%%%%%%%%%%%OOO\",\n\"OOOOOOOOOOOOOOOO\",\n\"OOOOOOOOOOOOOOOO\",\n\"%%%%%%%%%%%%%OOO\",\n\"%%%%%%%%%%%%OOO%\",\n\"%%%%%%%%%%%OOO%%\",\n\"%%%%%%%%%%OOO%%%\",\n\"%%%%%%%%%OOO%%%%\",\n\"%%%%%%%%OOO%%+++\",\n\"%%%%%%%OOO%%%%+%\",\n};\n/* XPM */\nstatic const char *const home_s_xpm[] = {\n\"16 16 23 1\",\n\"  c #282C2F\",\n\". c #272B30\",\n\"X c #272B32\",\n\"o c #272D30\",\n\"O c #272C32\",\n\"+ c #272D32\",\n\"@ c #292B30\",\n\"# c #282B31\",\n\"$ c #292B31\",\n\"% c #292B32\",\n\"& c #282C30\",\n\"* c #292C30\",\n\"= c #FF0000\",\n\"- c #292D30\",\n\"; c #282C31\",\n\": c #292C31\",\n\"> c #282D31\",\n\", c #292D31\",\n\"< c #282C32\",\n\"1 c #292C32\",\n\"2 c #282D32\",\n\"3 c #292D32\",\n\"4 c None\",\n/* pixels */\n\"4444444--4444444\",\n\"444444----444444\",\n\"44444--------444\",\n\"4444---44--4 444\",\n\"444---4444---444\",\n\"44---444444---44\",\n\"4---44444444---4\",\n\"---4444444444---\",\n\"---4444444444---\",\n\"4--4444444444--4\",\n\"4--4444==4444--4\",\n\"4--444====444--4\",\n\"4--444====444--4\",\n\"4--4444==4444--4\",\n\"---4444444444---\",\n\"----------------\"\n};\n/* XPM */\nstatic const char *const reload_s_xpm[] = {\n\"16 16 10 1\",\n\"  c #282B31\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #282C32\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c None\",\n/* pixels */\n\"%%%%%%%@@@@%%%%%\",\n\"%%%%%%@@@@@@%%%@\",\n\"%%%%%@@@%%@@@%@@\",\n\"%%%%@@@%%%%@@@@@\",\n\"%%%@@@%%%%%%@@@@\",\n\"%%@@@%%%%%%@@@@@\",\n\"%@@@%%%%%%@@@@@@\",\n\"@@@%%%%%%%%%%%%%\",\n\"%%%%%%%%%%%%%@@@\",\n\"%@@@@@@%%%%%@@@%\",\n\"%@@@@@%%%%%@@@%%\",\n\"%@@@@%%%%%@@@%%%\",\n\"%@@@@@%%%@@@%%%%\",\n\"%@@%@@@%@@@%%%%%\",\n\"%@%%%@@@@@%%%%%%\",\n\"%%%%%%@@@%%%%%%%\",\n};\n/* XPM */\nstatic const char *const save_s_xpm[] = {\n\"16 16 11 1\",\n\"  c #272C31\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #FF0000\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c #282E32\",\n\"& c None\",\n/* pixels */\n\"&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&\",\n\".&&&&&&&&&&&&&&.\",\n\"..&&&&&&&&&&&&..\",\n\"...&&&&&&&&&&...\",\n\"& ..&&++++&&...&\",\n\"&&...&&++&&...&&\",\n\"&&&...&&&&...&&&\",\n\"&&&&...&&...&&&&\",\n\"&&&&&......&&&&&\",\n\"&&&&&&....&&&&&&\",\n\"&&&&&&&..&&&&&&&\",\n\"&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&\",\n\"&&&&&&&&&&&&&&&&\"\n};\n/* XPM */\nstatic const char *const stop_s_xpm[] = {\n\"16 16 10 1\",\n\"  c #272D32\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #282C32\",\n\"@ c #FF0000\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c None\",\n/* pixels */\n\"%%%%$$$$$$$$%%%%\",\n\"%%$$$$$$$$$$$$%%\",\n\"%$$$%%%%%%%%$$$%\",\n\"%$$%%%%%%%%%%%$$%\",\n\"$$%%%%%%%%%%%%$$\",\n\"$$%%%@@%%@@%%%$$\",\n\"$$%%%@@@@@@%%%$$\",\n\"$$%%%%@@@@%%%%$$\",\n\"$$%%%%@@@@%%%%$$\",\n\"$$%%%@@@@@@%%%$$\",\n\"$$%%%@@%%@@%%%$$\",\n\"$$%%%%%%%%%%%%$$\",\n\"%$$%%%%%%%%%%$$%\",\n\"%$$$%%%%%%%%$$$%\",\n\"%%$$$$$$$$$$$$%%\",\n\"%%%%$$$$$$$$%%%%\"\n};\n\n/* XPM */\nstatic const char *const bm_s_xpm[] = {\n\"16 16 9 1\",\n\"  c #282C31\",\n\". c #292C31\",\n\"X c #282D31\",\n\"o c #292D31\",\n\"O c #282C32\",\n\"+ c #FF0000\",\n\"@ c #282D32\",\n\"# c #292D32\",\n\"$ c None\",\n/* pixels */\n\"$              $\",\n\"                \",\n\"   $$$$  $$+++  \",\n\"  $$$$$  $$++$  \",\n\"  $$$$$  $$+$$  \",\n\"  $$$$$  $$+$$  \",\n\"  $$ $$  $$$$$  \",\n\"  $$ $$  $$$$$  \",\n\"  $$$$$  $$$$$  \",\n\"  $$$$$  $$$$$  \",\n\"  $$ $$  $$$$$  \",\n\"  $$ $$  $$$$$  \",\n\"  $$$$$  $$$$$  \",\n\"   $$$$  $$$$   \",\n\"                \",\n\"$              $\"\n};\n\n/* XPM */\nstatic const char *const tools_s_xpm[] = {\n\"16 16 10 1\",\n\"  c #282C31\",\n\". c #292C31\",\n\"X c #282D31\",\n\"o c #292D31\",\n\"O c #282E31\",\n\"+ c #282C32\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #292D32\",\n\"% c None\",\n/* pixels */\n\"%%%%%XXXX%%%%%%%\",\n\"%%%%XXXXXX%%%%%%\",\n\"%%%XXX%%XXX%%%%%\",\n\"XXXXX%%%%XXXXXXX\",\n\"%XXXX%%%%XXXXXX%\",\n\"%%%XXX%%XXX%%%%%\",\n\"%%%%XXXXXX%%%%%%\",\n\"%%%%%XXXX%%%%%%%\",\n\"%%%%%%%XXXX%%%%%\",\n\"%%%%%%XXXXXX%%%%\",\n\"%%%%%XXX%%XXX%%%\",\n\"%XXXXXX%%%%XXXX%\",\n\"XXXXXXX%%%%XXXXX\",\n\"%%%%%XXX%%XXX%%%\",\n\"%%%%%%XXXXXX%%%%\",\n\"%%%%%%%XXXX%%%%%\"\n};\n\n/* XPM */\nstatic const char *const new_s_xpm[] = {\n\"11 11 10 1\",\n\"  c #272D32\",\n\". c #282C31\",\n\"X c #292C31\",\n\"o c #282D31\",\n\"O c #292D31\",\n\"+ c #282C32\",\n\"@ c #292C32\",\n\"# c #282D32\",\n\"$ c #FF0000\",\n\"% c None\",\n/* pixels */\n\"$$%%%%%%%$$\",\n\"$$$%%%%%$$$\",\n\"%$$$%%%$$$%\",\n\"%%$$$%$$$%%\",\n\"%%%$$$$$%%%\",\n\"%%%%$$$%%%%\",\n\"%%%$$$$$%%%\",\n\"%%$$$%$$$%%\",\n\"%$$$%%%$$$%\",\n\"$$$%%%%%$$$\",\n\"$$%%%%%%%$$\",\n};\n\n/* XPM */\nstatic const char *const search_xpm[] = {\n\"14 16 11 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #EEEEEE\",\n\"[      c #EE0000\",\n\"}      c #CC0000\",\n\"|      c #BB0000\",\n\"1      c #AA0000\",\n\"2      c #880000\",\n\"3      c #660000\",\n\"4      c #440000\",\n\"5      c #330000\",\n\"   .....      \",\n\"  ..    .     \",\n\" ..      .    \",\n\"..        .   \",\n\"..        .   \",\n\"..        .   \",\n\"..        .   \",\n\" ..      .    \",\n\"  ..    .     \",\n\"   ...545     \",\n\"       424    \",\n\"       313    \",\n\"        2|2   \",\n\"        2}2   \",\n\"         2}2  \",\n\"         11   \"};\n\n/* XPM */\nstatic const char *const help_xpm[] = {\n\"14 16 16 1\",\n\"       c None\",\n\"1      c #DBDBDB\",\n\"2      c #B6B6B6\",\n\"3      c #929292\",\n\"4      c #6D6D6D\",\n\"5      c #F1EFEF\",\n\"6      c #018B00\",\n\"7      c #000000\",\n\"8      c #A4BDA4\",\n\"9      c #FF0000\",\n\"A      c #000000\",\n\"B      c #000000\",\n\"C      c #000000\",\n\"D      c #000000\",\n\"E      c #000000\",\n\"F      c #000000\",\n\"              \",\n\"     9997     \",\n\"   99999997   \",\n\"  999   9997  \",\n\"  9997  9997  \",\n\"  9997  9997  \",\n\"   99  9997   \",\n\"       999    \",\n\"      999     \",\n\"     999      \",\n\"     99       \",\n\"              \",\n\"     99       \",\n\"    9977      \",\n\"    9977      \",\n\"     99       \"};\n\n/* XPM */\nstatic const char *const mini_bug_xpm[] = {\n\"16 16 7 1\",\n\"       c None\",\n\".      c #000000000000\",\n\"r      c #FFFF00000000\",\n\"X      c #BEFBC30BBEFB\",\n\"o      c #861782078617\",\n\"O      c #FFFFFFFFFFFF\",\n\"+      c #30C230C230C2\",\n\"                \",\n\"     .   .      \",\n\"      ...       \",\n\"    X.....X     \",\n\"    o.O...o     \",\n\"   o.O...o.o    \",\n\"   .rrrXrrr.    \",\n\"  .rrrrXrrrr.   \",\n\"   .rrrXrrr.    \",\n\"  .o.rrXrr.o.   \",\n\"    .rrXrr.     \",\n\"   .XrrXrrX.    \",\n\"      .o.       \",\n\"           .+++.\",\n\"            .o. \",\n\"             .  \"};\n\n/* XPM */\nstatic const char *const mini_ok_xpm[] = {\n\"15 15 5 1\",\n\"@ c #000000\",\n\"a c #808080\",\n\"b c #303030\",\n\"c c #606060\",\n\"  s none m none c none\",\n\"               \",\n\"               \",\n\"            @  \",\n\"           @@  \",\n\"          @@@  \",\n\"         @@@   \",\n\"   @@   @@@    \",\n\"  @@@  @@@     \",\n\"  @@@ @@@      \",\n\"  @@@@@@       \",\n\"  @@@@@        \",\n\"  @@@@         \",\n\"   @@     @bbb@\",\n\"           @a@ \",\n\"            @  \"\n};\n\n#if 0\n\n/* XPM */\nstatic const char *const left_i_xpm[] = {\n\"22 22 3 1\",\n\"  c None\",\n\". c #000000\",\n\"@ c gray70\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"        @@            \",\n\"       @@@            \",\n\"      @@@@            \",\n\"     @@@@@@@@@@ @@@@  \",\n\"    @@@@@@@@@@@ @@@@  \",\n\"   @@@@@@@@@@@@ @@@@  \",\n\"  @@@@@@@@@@@@@ @@@@  \",\n\"  @@@@@@@@@@@@@ @@@@  \",\n\"   @@@@@@@@@@@@ @@@@  \",\n\"    @@@@@@@@@@@ @@@@  \",\n\"     @@@@@@@@@@ @@@@  \",\n\"      @@@@            \",\n\"       @@@            \",\n\"        @@            \",\n\"                 @@@@@\",\n\"                  @ @ \",\n\"                   @  \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const right_i_xpm[] = {\n\"22 22 3 1\",\n\"  c None\",\n\". c #000000\",\n\"@ c gray70\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"            @@        \",\n\"            @@@       \",\n\"            @@@@      \",\n\"  @@@@ @@@@@@@@@@     \",\n\"  @@@@ @@@@@@@@@@@    \",\n\"  @@@@ @@@@@@@@@@@@   \",\n\"  @@@@ @@@@@@@@@@@@@  \",\n\"  @@@@ @@@@@@@@@@@@@  \",\n\"  @@@@ @@@@@@@@@@@@   \",\n\"  @@@@ @@@@@@@@@@@    \",\n\"  @@@@ @@@@@@@@@@     \",\n\"            @@@@      \",\n\"            @@@       \",\n\"            @@        \",\n\"                 @@@@@\",\n\"                  @ @ \",\n\"                   @  \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const stop_i_xpm[] = {\n/* columns rows colors chars-per-pixel */\n\"22 22 2 1\",\n\"       c None\",\n\"@      c gray70\",\n/* pixels */\n\"                      \",\n\"                      \",\n\"      @@@@@@@@@       \",\n\"     @@@@@@@@@@@      \",\n\"    @@@@@@@@@@@@@     \",\n\"   @@@@@@@@@@@@@@@    \",\n\"  @@@@@@@@@@@@@@@@@   \",\n\" @@@@@@@@@@@@@@@@@@@  \",\n\" @@@@@@  @@@  @@@@@@  \",\n\" @@@@@@   @   @@@@@@  \",\n\" @@@@@@@     @@@@@@@  \",\n\" @@@@@@@@   @@@@@@@@  \",\n\" @@@@@@@     @@@@@@@  \",\n\" @@@@@@   @   @@@@@@  \",\n\" @@@@@@  @@@  @@@@@@  \",\n\" @@@@@@@@@@@@@@@@@@@  \",\n\"  @@@@@@@@@@@@@@@@@   \",\n\"   @@@@@@@@@@@@@@@    \",\n\"    @@@@@@@@@@@@@     \",\n\"     @@@@@@@@@@@      \",\n\"      @@@@@@@@@       \",\n\"                      \"\n};\n\n/* XPM */\nstatic const char *const stop_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"    @@@@@@@@    \",\n\"   @@@@@@@@@@   \",\n\"  @@@@@@@@@@@@  \",\n\" @@@@@@@@@@@@@@ \",\n\"@@@@@@@@@@@@@@@@\",\n\"@@@@@ @@@@ @@@@@\",\n\"@@@@@@ @@ @@@@@@\",\n\"@@@@@@@  @@@@@@@\",\n\"@@@@@@@  @@@@@@@\",\n\"@@@@@@ @@ @@@@@@\",\n\"@@@@@ @@@@ @@@@@\",\n\"@@@@@@@@@@@@@@@@\",\n\" @@@@@@@@@@@@@@ \",\n\"  @@@@@@@@@@@@  \",\n\"   @@@@@@@@@@   \",\n\"    @@@@@@@@    \"};\n\n/* XPM */\nstatic const char *const left_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"                \",\n\"      @@        \",\n\"     @@@        \",\n\"    @@@@        \",\n\"   @@@@@@@@ @@@@\",\n\"  @@@@@@@@@ @@@@\",\n\" @@@@@@@@@@ @@@@\",\n\"@@@@@@@@@@@ @@@@\",\n\"@@@@@@@@@@@ @@@@\",\n\" @@@@@@@@@@ @@@@\",\n\"  @@@@@@@@@ @@@@\",\n\"   @@@@@@@@ @@@@\",\n\"    @@@@        \",\n\"     @@@   @@@@@\",\n\"      @@    @ @ \",\n\"             @  \"};\n\n/* XPM */\nstatic const char *const right_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"                \",\n\"        @@      \",\n\"        @@@     \",\n\"        @@@@    \",\n\"@@@@ @@@@@@@@   \",\n\"@@@@ @@@@@@@@@  \",\n\"@@@@ @@@@@@@@@@ \",\n\"@@@@ @@@@@@@@@@@\",\n\"@@@@ @@@@@@@@@@@\",\n\"@@@@ @@@@@@@@@@ \",\n\"@@@@ @@@@@@@@@  \",\n\"@@@@ @@@@@@@@   \",\n\"        @@@@    \",\n\"        @@@@@@@@\",\n\"        @@  @ @ \",\n\"             @  \"};\n#endif\n\n#endif /* __PIXMAPS_H__ */\n"
  },
  {
    "path": "src/pixmaps_classic.h",
    "content": "/*\n * File: pixmaps.h\n *\n * Copyright (C) 2000, 2001 Jorge Arellano Cid <jcid@dillo.org>\n * Design by John Grantham, Dipl.-Designer; http://www.grantham.de/\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef __PIXMAPS_H__\n#define __PIXMAPS_H__\n\n/* XPM */\nstatic const char *const left_xpm[] = {\n\"22 22 46 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #FF7856\",\n\"@      c #FF8361\",\n\"#      c #FF5325\",\n\"$      c #FF8C68\",\n\"%      c #FF6636\",\n\"&      c #FF6333\",\n\"*      c #FF936B\",\n\"=      c #FF6E39\",\n\"-      c #FF5111\",\n\";      c #FF8C61\",\n\">      c #FFA685\",\n\",      c #FF8659\",\n\"'      c #FF996E\",\n\")      c #FF773D\",\n\"!      c #FF5C16\",\n\"~      c #FF570F\",\n\"{      c #EC510E\",\n\"]      c #FF894C\",\n\"^      c #FF7E3B\",\n\"/      c #FF681A\",\n\"(      c #FF6414\",\n\"_      c #EC5D13\",\n\":      c #C56932\",\n\"<      c #CB5A15\",\n\"[      c #F86E19\",\n\"}      c #FF711A\",\n\"|      c #EC6918\",\n\"1      c #9F5F31\",\n\"2      c #C56118\",\n\"3      c #F87A1E\",\n\"4      c #FF7D1F\",\n\"5      c #EC741D\",\n\"6      c #9F6532\",\n\"7      c #C56A1C\",\n\"8      c #F88523\",\n\"9      c #8C4B14\",\n\"0      c #582F0C\",\n\"a      c #894A13\",\n\"b      c #9F6B35\",\n\"c      c #C57320\",\n\"d      c #B96C1E\",\n\"e      c #A47339\",\n\"f      c #C77C23\",\n\"g      c #93601C\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"        ..            \",\n\"       .+.            \",\n\"      .@#.            \",\n\"     .$%&...... ....  \",\n\"    .*=-=;>>>,. .,,.  \",\n\"   .')!~~~~~~{. .{{.  \",\n\"  .]^/(((((((_. .__.  \",\n\"  .:<[}}}}}}}|. .||.  \",\n\"   .1234444445. .55.  \",\n\"    .67879000a. .aa.  \",\n\"     .bcd...... ....  \",\n\"      .ef.            \",\n\"       .g.            \",\n\"        ..            \",\n\"                 .....\",\n\"                  .>. \",\n\"                   .  \",\n\"                      \"};\n/* XPM */\nstatic const char *const right_xpm[] = {\n\"22 22 46 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #569A59\",\n\"@      c #228126\",\n\"#      c #60A463\",\n\"$      c #2E9132\",\n\"%      c #319335\",\n\"&      c #65AE68\",\n\"*      c #52AC55\",\n\"=      c #80C282\",\n\"-      c #5BB15E\",\n\";      c #319C35\",\n\">      c #07880C\",\n\",      c #65B568\",\n\"'      c #008905\",\n\")      c #009405\",\n\"!      c #07970C\",\n\"~      c #31A935\",\n\"{      c #65BF68\",\n\"]      c #009705\",\n\"^      c #00A305\",\n\"/      c #07A60C\",\n\"(      c #2AB22E\",\n\"_      c #3CB83F\",\n\":      c #00A605\",\n\"<      c #00B305\",\n\"[      c #00AE05\",\n\"}      c #008E04\",\n\"|      c #219424\",\n\"1      c #00B405\",\n\"2      c #00C205\",\n\"3      c #00BD05\",\n\"4      c #009604\",\n\"5      c #218124\",\n\"6      c #007003\",\n\"7      c #004802\",\n\"8      c #007303\",\n\"9      c #00A104\",\n\"0      c #00CB05\",\n\"a      c #218924\",\n\"b      c #00A304\",\n\"c      c #00AD04\",\n\"d      c #219024\",\n\"e      c #00B804\",\n\"f      c #219B24\",\n\"g      c #008E03\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"            ..        \",\n\"            .+.       \",\n\"            .@#.      \",\n\"  .... ......$%&.     \",\n\"  .**. .*===-;>;,.    \",\n\"  .''. .'))))))!~{.   \",\n\"  .]]. .]^^^^^^^/(_.  \",\n\"  .::. .:<<<<<<<[}|.  \",\n\"  .11. .1222222345.   \",\n\"  .66. .67778909a.    \",\n\"  .... ......bcd.     \",\n\"            .ef.      \",\n\"            .g.       \",\n\"            ..        \",\n\"                 .....\",\n\"                  .=. \",\n\"                   .  \",\n\"                      \"};\n/* XPM */\nstatic const char *const reload_xpm[] = {\n\"22 22 147 2\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #BABABA\",\n\"@      c #CCCCCC\",\n\"#      c #CBCBCB\",\n\"$      c #5D5D5D\",\n\"%      c #909090\",\n\"&      c #9C9C9C\",\n\"*      c #A0A0A0\",\n\"=      c #EBEBEB\",\n\"-      c #4C4C4C\",\n\";      c #949494\",\n\">      c #8D8D8D\",\n\",      c #FFFFFF\",\n\"'      c #E4E4E4\",\n\")      c #424242\",\n\"!      c #989898\",\n\"~      c #A4A4A4\",\n\"{      c #878787\",\n\"]      c #FBFBFB\",\n\"^      c #E1E1E1\",\n\"/      c #393939\",\n\"(      c #9B9B9B\",\n\"_      c #A8A8A8\",\n\":      c #898989\",\n\"<      c #ECECEC\",\n\"[      c #DEDEDE\",\n\"}      c #313131\",\n\"|      c #0B0B0B\",\n\"1      c #0C0C0C\",\n\"2      c #2B2B2B\",\n\"3      c #575757\",\n\"4      c #8B8B8B\",\n\"5      c #AEAEAE\",\n\"6      c #8E8E8E\",\n\"7      c #DDDDDD\",\n\"8      c #2C2C2C\",\n\"9      c #282828\",\n\"0      c #666666\",\n\"a      c #A3A3A3\",\n\"b      c #C0C0C0\",\n\"c      c #7D7D7D\",\n\"d      c #3F3F3F\",\n\"e      c #1D1D1D\",\n\"f      c #6B6B6B\",\n\"g      c #929292\",\n\"h      c #585858\",\n\"i      c #BEBEBE\",\n\"j      c #4F4F4F\",\n\"k      c #A5A5A5\",\n\"l      c #BDBDBD\",\n\"m      c #B1B1B1\",\n\"n      c #A9A9A9\",\n\"o      c #AAAAAA\",\n\"p      c #696969\",\n\"q      c #0A0A0A\",\n\"r      c #5F5F5F\",\n\"s      c #515151\",\n\"t      c #545454\",\n\"u      c #686868\",\n\"v      c #868686\",\n\"w      c #B5B5B5\",\n\"x      c #B4B4B4\",\n\"y      c #7F907F\",\n\"z      c #436B43\",\n\"A      c #144F14\",\n\"B      c #004800\",\n\"C      c #1E1E1E\",\n\"D      c #838383\",\n\"E      c #9F9F9F\",\n\"F      c #262626\",\n\"G      c #9D9D9D\",\n\"H      c #B8B8B8\",\n\"I      c #839583\",\n\"J      c #577A57\",\n\"K      c #7E917E\",\n\"L      c #A7AAA7\",\n\"M      c #426D42\",\n\"N      c #004600\",\n\"O      c #A1A1A1\",\n\"P      c #404040\",\n\"Q      c #636363\",\n\"R      c #C6C6C6\",\n\"S      c #B7B7B7\",\n\"T      c #728C72\",\n\"U      c #B0B2B0\",\n\"V      c #777777\",\n\"W      c #323232\",\n\"X      c #C2C2C2\",\n\"Y      c #BCBCBC\",\n\"Z      c #8EA18E\",\n\"`      c #004B00\",\n\" .     c #979797\",\n\"..     c #0F0F0F\",\n\"+.     c #D2D2D2\",\n\"@.     c #AFAFAF\",\n\"#.     c #C4C4C4\",\n\"$.     c #B0B0B0\",\n\"%.     c #D9D9D9\",\n\"&.     c #C9C9C9\",\n\"*.     c #7A5200\",\n\"=.     c #B8AE9A\",\n\"-.     c #101010\",\n\";.     c #DFDFDF\",\n\">.     c #CECECE\",\n\",.     c #BFBFBF\",\n\"'.     c #D4D4D4\",\n\").     c #7F5500\",\n\"!.     c #D2D1CE\",\n\"~.     c #B4A585\",\n\"{.     c #E5E5E5\",\n\"].     c #845800\",\n\"^.     c #A58A53\",\n\"/.     c #C3B79F\",\n\"(.     c #D9D7D3\",\n\"_.     c #B09A6E\",\n\":.     c #C6BBA5\",\n\"<.     c #747474\",\n\"[.     c #3A3A3A\",\n\"}.     c #EAEAEA\",\n\"|.     c #222222\",\n\"1.     c #865A00\",\n\"2.     c #CCC1AA\",\n\"3.     c #AC915B\",\n\"4.     c #936C1C\",\n\"5.     c #E0E0E0\",\n\"6.     c #414141\",\n\"7.     c #787878\",\n\"8.     c #F0F0F0\",\n\"9.     c #8C8C8C\",\n\"0.     c #C3C3C3\",\n\"a.     c #E2E2E2\",\n\"b.     c #F4F4F4\",\n\"c.     c #646464\",\n\"d.     c #9E9E9E\",\n\"e.     c #DCDCDC\",\n\"f.     c #6A6A6A\",\n\"g.     c #555555\",\n\"h.     c #0E0E0E\",\n\"i.     c #F8F8F8\",\n\"j.     c #E6E6E6\",\n\"k.     c #191919\",\n\"l.     c #2F2F2F\",\n\"m.     c #464646\",\n\"n.     c #444444\",\n\"o.     c #242424\",\n\"p.     c #343434\",\n\"          . . . . . . . . . . .             \",\n\"          . + @ @ @ @ @ @ @ # $ .           \",\n\"          . % & & & & & & & * = - .         \",\n\"          . ; * * * * * * * > , ' ) .       \",\n\"          . ! ~ ~ ~ ~ ~ ~ ~ { ] , ^ / .     \",\n\"          . ( _ _ _ _ _ _ _ : < , , [ } .   \",\n\"          . | 1 1 1 2 3 4 5 6 < , , , 7 8 . \",\n\"      . 9 0 a b b a c d e f g : h h f * i . \",\n\"    . j k l m n n m l o p q r f s s t u v . \",\n\"  . - w x y z A A z y B w u C D & & & E * . \",\n\"  F G H I J K L K M N N H O P Q R R R R S . \",\n\". $ i S T U w w w B B B S i V W @ @ @ @ l . \",\n\". 6 X Y Z Y Y Y ` ` ` ` Y X  ...+.+.+.+.X . \",\n\". @.R #.#.#.#.#.#.#.#.#.#.R $...%.%.%.%.&.. \",\n\". n #.@ *.*.*.*.@ @ @ =.@ #.o -.;.;.;.;.>.. \",\n\". v ,.'.).).).'.'.'.!.~.'.,.% -.{.{.{.{.'.. \",\n\". 3 w '.].].^./.(./._.:.'.w <.[.}.}.}.}.%.. \",\n\"  |.{ X 1.2.3.4.4.3.2.5.X > 6.7.8.8.8.8.[ . \",\n\"  . [.9.0.a.}.}.}.}.a.0.> $ 9 X b.b.b.b.a.. \",\n\"    . W c.d.R e.e.R d.f.g.h.; i.i.i.i.i.j.. \",\n\"      . k.l.m.j j n.[.o.h.p.3 3 3 3 3 3 { . \",\n\"          . . . . . . . . . . . . . . . . . \"};\n/* XPM */\nstatic const char *const home_xpm[] = {\n\"22 22 106 2\",\n\"       c None\",\n\".      c #190E0B\",\n\"+      c #180D09\",\n\"@      c #D8947D\",\n\"#      c #000000\",\n\"$      c #190D09\",\n\"%      c #CC6746\",\n\"&      c #A7401F\",\n\"*      c #C65834\",\n\"=      c #1A0D09\",\n\"-      c #D16746\",\n\";      c #A63F1F\",\n\">      c #160D09\",\n\",      c #C13206\",\n\"'      c #D86746\",\n\")      c #AD3F1F\",\n\"!      c #1C0F0A\",\n\"~      c #9B9376\",\n\"{      c #D2502A\",\n\"]      c #1B0D09\",\n\"^      c #DF6746\",\n\"/      c #B43F1F\",\n\"(      c #999174\",\n\"_      c #C1B588\",\n\":      c #D34721\",\n\"<      c #D8461E\",\n\"[      c #1C0D09\",\n\"}      c #E76746\",\n\"|      c #BB3F1F\",\n\"1      c #1D0F0A\",\n\"2      c #C5B98B\",\n\"3      c #C4B889\",\n\"4      c #93371F\",\n\"5      c #E14821\",\n\"6      c #1B0803\",\n\"7      c #1D0D09\",\n\"8      c #EE6746\",\n\"9      c #C33F1F\",\n\"0      c #1E0F0A\",\n\"a      c #9E9678\",\n\"b      c #CABD8F\",\n\"c      c #CCBF8E\",\n\"d      c #CFC290\",\n\"e      c #1E0D09\",\n\"f      c #F56746\",\n\"g      c #CA3F1F\",\n\"h      c #21110A\",\n\"i      c #A09879\",\n\"j      c #CDC091\",\n\"k      c #D2C593\",\n\"l      c #D8CA97\",\n\"m      c #DCCE9A\",\n\"n      c #1D0500\",\n\"o      c #C63F21\",\n\"p      c #9E361F\",\n\"q      c #130300\",\n\"r      c #988E6A\",\n\"s      c #D1C492\",\n\"t      c #D5C895\",\n\"u      c #DDCF9A\",\n\"v      c #E4D69F\",\n\"w      c #E9DAA3\",\n\"x      c #C9BC8C\",\n\"y      c #DACC98\",\n\"z      c #E0D29C\",\n\"A      c #E8D9A2\",\n\"B      c #EFE0A7\",\n\"C      c #F2E3A9\",\n\"D      c #5E5842\",\n\"E      c #676047\",\n\"F      c #F9E9AE\",\n\"G      c #655F46\",\n\"H      c #DBCD99\",\n\"I      c #C16C4E\",\n\"J      c #D3927C\",\n\"K      c #C86F51\",\n\"L      c #FCECB0\",\n\"M      c #D6D6D6\",\n\"N      c #D2D2D2\",\n\"O      c #E3D49F\",\n\"P      c #A82900\",\n\"Q      c #B82C00\",\n\"R      c #AB2A00\",\n\"S      c #FEEEB1\",\n\"T      c #6F6D6D\",\n\"U      c #6D6B6B\",\n\"V      c #E7D9A2\",\n\"W      c #B52A00\",\n\"X      c #C42D00\",\n\"Y      c #B62A00\",\n\"Z      c #FFEFB2\",\n\"`      c #696249\",\n\" .     c #676048\",\n\"..     c #EADBA4\",\n\"+.     c #C12A00\",\n\"@.     c #D22D00\",\n\"#.     c #C22A00\",\n\"$.     c #ECDDA5\",\n\"%.     c #CE2A00\",\n\"&.     c #DF2D00\",\n\"*.     c #DA2A00\",\n\"=.     c #EC2D00\",\n\"-.     c #898060\",\n\";.     c #851800\",\n\">.     c #551000\",\n\",.     c #58523D\",\n\"                                            \",\n\"                    . .                     \",\n\"                  + @ @ +     # # #         \",\n\"                $ % & & % $   # * #         \",\n\"              = - ; > > ; - = # , #         \",\n\"            = ' ) ! ~ ~ ! ) ' = { #         \",\n\"          ] ^ / ! ( _ _ ( ! / : < #         \",\n\"        [ } | 1 ~ 2 3 3 2 ~ 1 4 5 6         \",\n\"      7 8 9 0 a b c d d c b a 0 9 8 7       \",\n\"    e f g h i j k l m m l k j i h g f e     \",\n\"  n o p q r s t u v w w v u t s r q p o n   \",\n\"  # # # # x y z A B C C B A z y x # # # #   \",\n\"        # s D # # # E F G # # D s #         \",\n\"        # H # I J K # L # M N # H #         \",\n\"        # O # P Q R # S # T U # O #         \",\n\"        # V # W X Y # Z ` # #  .V #         \",\n\"        # ..# +.@.#.# Z Z Z S S ..#         \",\n\"        # $.# %.&.%.# Z Z Z Z Z $.#         \",\n\"        # $.# *.=.*.# Z Z Z Z Z $.#         \",\n\"        # -.# ;.>.;.# ,.,.,.,.,.-.#         \",\n\"        # # # # # # # # # # # # # #         \",\n\"                                            \"};\n/* XPM */\nstatic const char *const save_xpm[] = {\n\"22 22 59 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #638163\",\n\"@      c #4D5A4D\",\n\"#      c #A4BDA4\",\n\"$      c #184318\",\n\"%      c #0E280E\",\n\"&      c #4B7E4B\",\n\"*      c #194419\",\n\"=      c #0F290F\",\n\"-      c #508350\",\n\";      c #1B481B\",\n\">      c #102B10\",\n\",      c #568956\",\n\"'      c #1D4C1D\",\n\")      c #112D11\",\n\"!      c #5D905D\",\n\"~      c #205020\",\n\"{      c #133013\",\n\"]      c #639663\",\n\"^      c #235223\",\n\"/      c #153115\",\n\"(      c #6B9E6B\",\n\"_      c #275627\",\n\":      c #173317\",\n\"<      c #73A673\",\n\"[      c #2B5A2B\",\n\"}      c #2E612E\",\n\"|      c #193619\",\n\"1      c #2E5D2E\",\n\"2      c #152A15\",\n\"3      c #326132\",\n\"4      c #838383\",\n\"5      c #9D9D9D\",\n\"6      c #B3B3B3\",\n\"7      c #979797\",\n\"8      c #366536\",\n\"9      c #585858\",\n\"0      c #565656\",\n\"a      c #727272\",\n\"b      c #6A6A6A\",\n\"c      c #386838\",\n\"d      c #848484\",\n\"e      c #7A7A7A\",\n\"f      c #3C6B3C\",\n\"g      c #999999\",\n\"h      c #8E8E8E\",\n\"i      c #366036\",\n\"j      c #3F6E3F\",\n\"k      c #BDBDBD\",\n\"l      c #BABABA\",\n\"m      c #ADADAD\",\n\"n      c #A0A0A0\",\n\"o      c #162616\",\n\"p      c #264126\",\n\"q      c #949494\",\n\"r      c #696969\",\n\"s      c #424242\",\n\"t      c #676767\",\n\"                      \",\n\"                      \",\n\"   ................   \",\n\"  .+@############@+.  \",\n\"  .$%&&&&&&&&&&&&%$.  \",\n\"  .*=------------=*.  \",\n\"  .;>,,,,,,,,,,,,>;.  \",\n\"  .')!!!!!!!!!!!!)'.  \",\n\"  .~{]]]]]]]]]]]]{~.  \",\n\"  .^/((((((((((((/^.  \",\n\"  ._:<<<<<<<<<<<<:_.  \",\n\"  .[}||||||||||||}[.  \",\n\"  .12............21.  \",\n\"  .3.444566666667.3.  \",\n\"  .8.9..0aaaaaaab.8.  \",\n\"  .c.d..eddddddde.c.  \",\n\"  .f.g..hgggggggh.i.  \",\n\"  .j.k..lmmmmmmmn.o.  \",\n\"  .p.qqqrssssssst..   \",\n\"   ...............    \",\n\"                      \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const stop_xpm[] = {\n\"22 22 77 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #703434\",\n\"@      c #E57F7F\",\n\"#      c #E68080\",\n\"$      c #6F2929\",\n\"%      c #DB4343\",\n\"&      c #D31A1A\",\n\"*      c #CE0000\",\n\"=      c #DC4343\",\n\"-      c #D51A1A\",\n\";      c #D00000\",\n\">      c #702929\",\n\",      c #DF4343\",\n\"'      c #D71A1A\",\n\")      c #D30000\",\n\"!      c #6F1B1B\",\n\"~      c #E14343\",\n\"{      c #DA1A1A\",\n\"]      c #D60000\",\n\"^      c #A66666\",\n\"/      c #841717\",\n\"(      c #DA0404\",\n\"_      c #DD1A1A\",\n\":      c #D90000\",\n\"<      c #D56B6B\",\n\"[      c #FFFFFF\",\n\"}      c #E7D5D5\",\n\"|      c #861717\",\n\"1      c #CC0000\",\n\"2      c #DC0000\",\n\"3      c #E64C4C\",\n\"4      c #FADEDE\",\n\"5      c #EAD5D5\",\n\"6      c #722C2C\",\n\"7      c #CF0000\",\n\"8      c #E00000\",\n\"9      c #EB5C5C\",\n\"0      c #FBDFDF\",\n\"a      c #FDF8F8\",\n\"b      c #E40000\",\n\"c      c #DE3535\",\n\"d      c #FEF8F8\",\n\"e      c #E70000\",\n\"f      c #971C1C\",\n\"g      c #EED5D5\",\n\"h      c #FEFAFA\",\n\"i      c #EB0000\",\n\"j      c #AA1C1C\",\n\"k      c #EFD5D5\",\n\"l      c #FDE3E3\",\n\"m      c #F69595\",\n\"n      c #C50000\",\n\"o      c #D50000\",\n\"p      c #EF0000\",\n\"q      c #F77777\",\n\"r      c #FDE4E4\",\n\"s      c #F56565\",\n\"t      c #4D0000\",\n\"u      c #9E0000\",\n\"v      c #D70000\",\n\"w      c #F20000\",\n\"x      c #FAA7A7\",\n\"y      c #F76969\",\n\"z      c #420000\",\n\"A      c #A00000\",\n\"B      c #DA0000\",\n\"C      c #F50000\",\n\"D      c #A20000\",\n\"E      c #DD0000\",\n\"F      c #F80000\",\n\"G      c #430000\",\n\"H      c #A40000\",\n\"I      c #DF0000\",\n\"J      c #FB0000\",\n\"K      c #380000\",\n\"L      c #570000\",\n\"                      \",\n\"                      \",\n\"      .........       \",\n\"     .+@#####@+.      \",\n\"    .$%&*****&%$.     \",\n\"   .$=-;;;;;;;-=$.    \",\n\"  .>,')))))))))',>.   \",\n\" .!~{]]^/]]]/^]]{~!.  \",\n\" .(_::<[}|:|}[<::_(.  \",\n\" .122234[565[432221.  \",\n\" .7888890[a[0988887.  \",\n\" .)bbbbbcd[dcbbbbb).  \",\n\" .]eeeefg[h[gfeeee].  \",\n\" .:iiijk[lml[kjiii:.  \",\n\" .noppq[rspsr[qppon.  \",\n\" .tuvwwxywwwyxwwvut.  \",\n\"  .zABCCCCCCCCCBAz.   \",\n\"   .zDEFFFFFFFEDz.    \",\n\"    .GHIJJJJJIHG.     \",\n\"     .KLLLLLLLK.      \",\n\"      .........       \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const bm_xpm[] = {\n\"22 22 86 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #150B0A\",\n\"@      c #A46C6A\",\n\"#      c #B15652\",\n\"$      c #C68380\",\n\"%      c #840600\",\n\"&      c #8F0600\",\n\"*      c #979797\",\n\"=      c #B3B3B3\",\n\"-      c #880600\",\n\";      c #930600\",\n\">      c #616161\",\n\",      c #696969\",\n\"'      c #595959\",\n\")      c #8D0600\",\n\"!      c #980600\",\n\"~      c #666666\",\n\"{      c #6E6E6E\",\n\"]      c #5E5E5E\",\n\"^      c #920600\",\n\"/      c #9E0700\",\n\"(      c #6B6B6B\",\n\"_      c #747474\",\n\":      c #636363\",\n\"<      c #970600\",\n\"[      c #A30700\",\n\"}      c #727272\",\n\"|      c #7B7B7B\",\n\"1      c #9D0600\",\n\"2      c #AA0700\",\n\"3      c #777777\",\n\"4      c #818181\",\n\"5      c #A40600\",\n\"6      c #B10700\",\n\"7      c #7F7F7F\",\n\"8      c #898989\",\n\"9      c #757575\",\n\"0      c #A90700\",\n\"a      c #B70800\",\n\"b      c #868686\",\n\"c      c #919191\",\n\"d      c #7C7C7C\",\n\"e      c #BF0800\",\n\"f      c #8E8E8E\",\n\"g      c #999999\",\n\"h      c #828282\",\n\"i      c #C60900\",\n\"j      c #959595\",\n\"k      c #A1A1A1\",\n\"l      c #BD0800\",\n\"m      c #CC0900\",\n\"n      c #9C9C9C\",\n\"o      c #A9A9A9\",\n\"p      c #909090\",\n\"q      c #C40800\",\n\"r      c #D40900\",\n\"s      c #A3A3A3\",\n\"t      c #B0B0B0\",\n\"u      c #969696\",\n\"v      c #CA0900\",\n\"w      c #DA0A00\",\n\"x      c #B7B7B7\",\n\"y      c #D00900\",\n\"z      c #E10A00\",\n\"A      c #AFAFAF\",\n\"B      c #BDBDBD\",\n\"C      c #D70900\",\n\"D      c #E80A00\",\n\"E      c #B4B4B4\",\n\"F      c #C3C3C3\",\n\"G      c #A6A6A6\",\n\"H      c #DB0900\",\n\"I      c #E10900\",\n\"J      c #454545\",\n\"K      c #CD0900\",\n\"L      c #A00700\",\n\"M      c #7B1510\",\n\"N      c #A40700\",\n\"O      c #A20700\",\n\"P      c #3E0300\",\n\"Q      c #4A0300\",\n\"R      c #A90800\",\n\"S      c #540400\",\n\"T      c #010000\",\n\"U      c #660400\",\n\"         ......       \",\n\"        +@#$$$#.      \",\n\"   .......%&&&%....   \",\n\"   .*====.-;;;-.=*.   \",\n\"   .>,',,.)!!!).,>.   \",\n\"   .~{]{{.^///^.{~.   \",\n\"   .(_:__.<[[[<._(.   \",\n\"   .}|,||.12221.|}.   \",\n\"   .34{44.56665.43.   \",\n\"   .78988.0aaa0.87.   \",\n\"   .bcdcc.6eee6.cb.   \",\n\"   .fghgg.aiiia.gf.   \",\n\"   .jk8kk.lmmml.kj.   \",\n\"   .nopoo.qrrrq.on.   \",\n\"   .stutt.vwwwv.ts.   \",\n\"   .oxnxx.yzzzy.xo.   \",\n\"   .ABkBB.CDDDC.BA.   \",\n\"   .EFGFF.HIIIH.FE.   \",\n\"   .(JJJJ.KLMNy.J(.   \",\n\"   .......OP.QR....   \",\n\"         .S. TU.      \",\n\"          .   .       \"};\n\n/* XPM */\nstatic const char *const tools_xpm[] = {\n\"22 22 25 1\",\n\"       c #None\",\n\".      c #696977777676\",\n\"X      c #96969B9B9B9B\",\n\"o      c #848490908F8F\",\n\"O      c #D7D7D7D7D7D7\",\n\"+      c #B5B5B7B7B7B7\",\n\"@      c #EAEAEAEAEAEA\",\n\"#      c #797987878787\",\n\"$      c #F1F1EFEFEFEF\",\n\"%      c #CCCCCCCCCCCC\",\n\"&      c #8B8B97979696\",\n\"*      c #CACACACACACA\",\n\"=      c #787878787878\",\n\"-      c #AAAAB5B5B5B5\",\n\";      c #F9F9F8F8F9F9\",\n\":      c #A6A6A8A8A8A8\",\n\">      c #7F7F90907F7F\",\n\",      c #A4A4BDBDA4A4\",\n\"<      c #A5A58A8A5353\",\n\"1      c #00008B8B0000\",\n\"2      c #6C6C6C6C6C6C\",\n\"3      c #BCBCC3C3C2C2\",\n\"4      c #CACAD8D8D8D8\",\n\"5      c #9999A8A8A8A8\",\n\"6      c #848489898989\",\n\"                      \",\n\"                      \",\n\"        .XXo.         \",\n\"        oO$$$+.       \",\n\"        oo@$$$@#      \",\n\"         #o@$$$O#     \",\n\"          #X$$$$%X    \",\n\"  &#       #X$$$@o    \",\n\"  #*o      =-@$$$#    \",\n\"  oO*o    #$@$$$$#    \",\n\"  oOOOX  oO@;$$$$X    \",\n\"  #@O*OXo:>,<$$$$o    \",\n\"  XO@O*O*12>,<$$$#    \",\n\"   X$@O*+<12>,<$$&    \",\n\"    +$@O*+<12>,<;@    \",\n\"     :$@O*+<12>,<$@   \",\n\"      &34O*-<11>,<$$  \",\n\"        -X&X&<12>,<$O \",\n\"            45<11>,<3 \",\n\"             45<1=>O# \",\n\"              -&X6&=# \",\n\"                      \"};\n\n/* Small icons here */\n\n/* XPM */\nstatic const char *const left_s_xpm[] = {\n\"16 16 33 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #1F120A\",\n\"@      c #FF9A59\",\n\"#      c #DE7E42\",\n\"$      c #FF7A26\",\n\"%      c #DE8247\",\n\"&      c #FF8335\",\n\"*      c #FF8232\",\n\"=      c #FF6A0C\",\n\"-      c #FF9D5E\",\n\";      c #FFB382\",\n\">      c #FF9755\",\n\",      c #FF6605\",\n\"'      c #EC5E05\",\n\")      c #1F0F05\",\n\"!      c #DE691E\",\n\"~      c #FF7F2E\",\n\"{      c #180A00\",\n\"]      c #A44103\",\n\"^      c #CB5104\",\n\"/      c #F86305\",\n\"(      c #120700\",\n\"_      c #7E3203\",\n\":      c #C54F04\",\n\"<      c #8C3803\",\n\"[      c #582302\",\n\"}      c #893703\",\n\"|      c #B94A04\",\n\"1      c #833403\",\n\"2      c #C75004\",\n\"3      c #110700\",\n\"4      c #933B03\",\n\"                \",\n\"      ..        \",\n\"     +@.        \",\n\"    +#$.        \",\n\"   +%&*.... ....\",\n\"  +%&=&-;>. .>>.\",\n\" +%&=,,,,'. .''.\",\n\")!~=,,,,,'. .''.\",\n\"{]^/,,,,,'. .''.\",\n\" (_:/,,,,'. .''.\",\n\"  (_:/:<[}. .}}.\",\n\"   (_:|.... ....\",\n\"    (12.        \",\n\"     34.   .....\",\n\"      ..    .;. \",\n\"             .  \"};\n/* XPM */\nstatic const char *const right_s_xpm[] = {\n\"16 16 56 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #569A59\",\n\"@      c #0A120A\",\n\"#      c #228126\",\n\"$      c #3F8342\",\n\"%      c #09130A\",\n\"&      c #2E9132\",\n\"*      c #319335\",\n\"=      c #448D47\",\n\"-      c #09140A\",\n\";      c #52AC55\",\n\">      c #80C282\",\n\",      c #5BB15E\",\n\"'      c #319C35\",\n\")      c #07880C\",\n\"!      c #449447\",\n\"~      c #09150A\",\n\"{      c #008905\",\n\"]      c #009405\",\n\"^      c #07970C\",\n\"/      c #31A935\",\n\"(      c #449E47\",\n\"_      c #09160A\",\n\":      c #009705\",\n\"<      c #00A305\",\n\"[      c #07A60C\",\n\"}      c #2AB22E\",\n\"|      c #1B971E\",\n\"1      c #041505\",\n\"2      c #00A605\",\n\"3      c #00B305\",\n\"4      c #00AE05\",\n\"5      c #008E04\",\n\"6      c #007303\",\n\"7      c #001100\",\n\"8      c #00B405\",\n\"9      c #00C205\",\n\"0      c #00BD05\",\n\"a      c #009604\",\n\"b      c #006003\",\n\"c      c #000D00\",\n\"d      c #007003\",\n\"e      c #004802\",\n\"f      c #00A104\",\n\"g      c #00CB05\",\n\"h      c #006803\",\n\"i      c #000E00\",\n\"j      c #00A304\",\n\"k      c #00AD04\",\n\"l      c #006F03\",\n\"m      c #000F00\",\n\"n      c #00B804\",\n\"o      c #007A03\",\n\"p      c #001000\",\n\"q      c #008E03\",\n\"                \",\n\"        ..      \",\n\"        .+@     \",\n\"        .#$%    \",\n\".... ....&*=-   \",\n\".;;. .;>,')'!~  \",\n\".{{. .{]]]]^/(_ \",\n\".::. .:<<<<<[}|1\",\n\".22. .2333334567\",\n\".88. .899990abc \",\n\".dd. .de6fgfhi  \",\n\".... ....jklm   \",\n\"        .nop    \",\n\"        .qpb....\",\n\"        ..  .>. \",\n\"             .  \"};\n/* XPM */\nstatic const char *const home_s_xpm[] = {\n\"16 16 54 1\",\n\"       c None\",\n\".      c #170E0B\",\n\"+      c #000000\",\n\"@      c #170C09\",\n\"#      c #B0705B\",\n\"$      c #B35636\",\n\"%      c #A13F20\",\n\"&      c #661B03\",\n\"*      c #AA3009\",\n\"=      c #190C09\",\n\"-      c #AC3F20\",\n\";      c #721C03\",\n\">      c #8E8875\",\n\",      c #C24F2D\",\n\"'      c #1A0C09\",\n\")      c #B93F20\",\n\"!      c #7C1C03\",\n\"~      c #8F876F\",\n\"{      c #A9A081\",\n\"]      c #A72403\",\n\"^      c #CE4522\",\n\"/      c #1B0C09\",\n\"(      c #C63F20\",\n\"_      c #861C03\",\n\":      c #A9A080\",\n\"<      c #FDEDB3\",\n\"[      c #631503\",\n\"}      c #CC2903\",\n\"|      c #1B0703\",\n\"1      c #1D0C08\",\n\"2      c #D03E20\",\n\"3      c #871902\",\n\"4      c #847C67\",\n\"5      c #FFEFB2\",\n\"6      c #847C68\",\n\"7      c #851902\",\n\"8      c #CF3A1B\",\n\"9      c #A81E02\",\n\"0      c #721402\",\n\"a      c #9A9070\",\n\"b      c #8B8163\",\n\"c      c #9C926D\",\n\"d      c #FDEDB1\",\n\"e      c #EADBA4\",\n\"f      c #696249\",\n\"g      c #ECDDA5\",\n\"h      c #C25834\",\n\"i      c #FFFFFF\",\n\"j      c #BE2D00\",\n\"k      c #BF0000\",\n\"l      c #898060\",\n\"m      c #58523D\",\n\"n      c #D90000\",\n\"o      c #BB0000\",\n\"                \",\n\"       ..  +++  \",\n\"      @##@ +$+  \",\n\"     @%&&%@+*+  \",\n\"    =-;>>;-=,+  \",\n\"   ')!~{{~!]^+  \",\n\"  /(_~:<<:~[}|  \",\n\" 1234:<55<:6781 \",\n\"+90a{<5555<:b09+\",\n\"+++cd555555dc+++\",\n\"  +e5f+f5f+fe+  \",\n\"  +g5+h+5+i+g+  \",\n\"  +g5+j+5f+fg+  \",\n\"  +g5+k+5555g+  \",\n\"  +lm+n+mmmml+  \",\n\"  ++++o+++++++  \"};\n/* XPM */\nstatic const char *const reload_s_xpm[] = {\n\"16 16 74 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #282828\",\n\"@      c #666666\",\n\"#      c #9B9B9B\",\n\"$      c #BFBFBF\",\n\"%      c #4F4F4F\",\n\"&      c #A5A5A5\",\n\"*      c #BDBDBD\",\n\"=      c #B1B1B1\",\n\"-      c #A9A9A9\",\n\";      c #4C4C4C\",\n\">      c #B5B5B5\",\n\",      c #B4B4B4\",\n\"'      c #7F907F\",\n\")      c #436B43\",\n\"!      c #144F14\",\n\"~      c #004800\",\n\"{      c #262626\",\n\"]      c #9D9D9D\",\n\"^      c #B8B8B8\",\n\"/      c #839583\",\n\"(      c #577A57\",\n\"_      c #7E917E\",\n\":      c #A7AAA7\",\n\"<      c #426D42\",\n\"[      c #004600\",\n\"}      c #5D5D5D\",\n\"|      c #BEBEBE\",\n\"1      c #B7B7B7\",\n\"2      c #728C72\",\n\"3      c #B0B2B0\",\n\"4      c #8E8E8E\",\n\"5      c #C2C2C2\",\n\"6      c #BCBCBC\",\n\"7      c #8EA18E\",\n\"8      c #004B00\",\n\"9      c #AFAFAF\",\n\"0      c #C6C6C6\",\n\"a      c #C4C4C4\",\n\"b      c #CCCCCC\",\n\"c      c #7A5200\",\n\"d      c #B8AE9A\",\n\"e      c #868686\",\n\"f      c #D4D4D4\",\n\"g      c #7F5500\",\n\"h      c #D2D1CE\",\n\"i      c #B4A585\",\n\"j      c #575757\",\n\"k      c #845800\",\n\"l      c #A58A53\",\n\"m      c #C3B79F\",\n\"n      c #D9D7D3\",\n\"o      c #B09A6E\",\n\"p      c #C6BBA5\",\n\"q      c #222222\",\n\"r      c #878787\",\n\"s      c #865A00\",\n\"t      c #CCC1AA\",\n\"u      c #AC915B\",\n\"v      c #936C1C\",\n\"w      c #E0E0E0\",\n\"x      c #3A3A3A\",\n\"y      c #8C8C8C\",\n\"z      c #C3C3C3\",\n\"A      c #E2E2E2\",\n\"B      c #EAEAEA\",\n\"C      c #323232\",\n\"D      c #646464\",\n\"E      c #9E9E9E\",\n\"F      c #DCDCDC\",\n\"G      c #191919\",\n\"H      c #2F2F2F\",\n\"I      c #404040\",\n\"     ......     \",\n\"   .+@#$$#@+.   \",\n\"  .%&*=--=*&%.  \",\n\" .;>,')!!)'~>;. \",\n\" {]^/(_:_<[[^]{ \",\n\".}|123>>>~~~1|}.\",\n\".45676668888654.\",\n\".90aaaaaaaaaa09.\",\n\".-abccccbbbdba-.\",\n\".e$fgggfffhif$e.\",\n\".j>fkklmnmopf>j.\",\n\" qr5stuvvutw5rq \",\n\" .xyzABBBBAzyx. \",\n\"  .CDE0FF0EDC.  \",\n\"   .GHI%%IHG.   \",\n\"     ......     \"};\n/* XPM */\nstatic const char *const save_s_xpm[] = {\n\"16 16 51 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #638163\",\n\"@      c #8CA28C\",\n\"#      c #A4BDA4\",\n\"$      c #184318\",\n\"%      c #1A481A\",\n\"&      c #4C7F4C\",\n\"*      c #1A461A\",\n\"=      c #1C4C1C\",\n\"-      c #528552\",\n\";      c #1B4A1B\",\n\">      c #1D501D\",\n\",      c #598C59\",\n\"'      c #1F4E1F\",\n\")      c #215421\",\n\"!      c #619461\",\n\"~      c #225122\",\n\"{      c #255825\",\n\"]      c #699C69\",\n\"^      c #275627\",\n\"/      c #2A5D2A\",\n\"(      c #72A572\",\n\"_      c #2B5A2B\",\n\":      c #2E612E\",\n\"<      c #2F5E2F\",\n\"[      c #152A15\",\n\"}      c #336233\",\n\"|      c #838383\",\n\"1      c #9D9D9D\",\n\"2      c #B3B3B3\",\n\"3      c #979797\",\n\"4      c #376637\",\n\"5      c #5A5A5A\",\n\"6      c #585858\",\n\"7      c #757575\",\n\"8      c #6C6C6C\",\n\"9      c #3A6A3A\",\n\"0      c #8C8C8C\",\n\"a      c #828282\",\n\"b      c #345E34\",\n\"c      c #3E6D3E\",\n\"d      c #B6B6B6\",\n\"e      c #A5A5A5\",\n\"f      c #999999\",\n\"g      c #162616\",\n\"h      c #264126\",\n\"i      c #919191\",\n\"j      c #676767\",\n\"k      c #414141\",\n\"l      c #656565\",\n\"................\",\n\".+@##########@+.\",\n\".$%&&&&&&&&&&%$.\",\n\".*=----------=*.\",\n\".;>,,,,,,,,,,>;.\",\n\".')!!!!!!!!!!)'.\",\n\".~{]]]]]]]]]]{~.\",\n\".^/((((((((((/^.\",\n\"._::::::::::::_.\",\n\".<[..........[<.\",\n\".}.|||1222223.}.\",\n\".4.5..6777778.4.\",\n\".9.0..a00000a.b.\",\n\".c.d..2eeeeef.g.\",\n\".h.iiijkkkkkl.. \",\n\" .............  \"};\n/* XPM */\nstatic const char *const stop_s_xpm[] = {\n\"16 16 65 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #5A3435\",\n\"@      c #BF7F80\",\n\"#      c #BF8081\",\n\"$      c #562929\",\n\"%      c #A54344\",\n\"&      c #911A1C\",\n\"*      c #850002\",\n\"=      c #582929\",\n\"-      c #AA4344\",\n\";      c #981A1C\",\n\">      c #8C0002\",\n\",      c #551B1C\",\n\"'      c #B04345\",\n\")      c #9F1A1D\",\n\"!      c #940003\",\n\"~      c #926667\",\n\"{      c #661C1E\",\n\"]      c #A00407\",\n\"^      c #A81A1D\",\n\"/      c #9E0003\",\n\"(      c #B86B6D\",\n\"_      c #FFFFFF\",\n\":      c #E2D5D5\",\n\"<      c #581C1D\",\n\"[      c #9C0003\",\n\"}      c #A90003\",\n\"|      c #C24C4E\",\n\"1      c #F4DDDE\",\n\"2      c #E9DADA\",\n\"3      c #A70003\",\n\"4      c #B40003\",\n\"5      c #C4383A\",\n\"6      c #F5DEDE\",\n\"7      c #B10004\",\n\"8      c #BF0004\",\n\"9      c #9A1C1F\",\n\"0      c #F0D9D9\",\n\"a      c #BB0004\",\n\"b      c #CA0004\",\n\"c      c #971C1F\",\n\"d      c #ECD5D5\",\n\"e      c #FAE6E6\",\n\"f      c #AF0003\",\n\"g      c #BE0004\",\n\"h      c #D50004\",\n\"i      c #E87779\",\n\"j      c #FBE5E5\",\n\"k      c #EA7E80\",\n\"l      c #470001\",\n\"m      c #930003\",\n\"n      c #C70004\",\n\"o      c #E00004\",\n\"p      c #F5A7A9\",\n\"q      c #EC6568\",\n\"r      c #3F0001\",\n\"s      c #990003\",\n\"t      c #D00004\",\n\"u      c #EA0004\",\n\"v      c #410001\",\n\"w      c #D70004\",\n\"x      c #F20005\",\n\"y      c #370001\",\n\"z      c #560002\",\n\"    ........    \",\n\"   .+@####@+.   \",\n\"  .$%&****&%$.  \",\n\" .=-;>>>>>>;-=. \",\n\".,')!~{!!{~!)',.\",\n\".]^/(_:<<:_(/^].\",\n\".[}}|1_22_1|}}[.\",\n\".344456__654443.\",\n\".788890__098887.\",\n\".abbcd_ee_dcbba.\",\n\".fghi_jkkj_ihgf.\",\n\".lmnopqooqponml.\",\n\" .rstuuuuuutsr. \",\n\"  .v/wxxxxw/v.  \",\n\"   .yzzzzzzy.   \",\n\"    ........    \"};\n\n/* XPM */\nstatic const char *const bm_s_xpm[] = {\n\"16 16 63 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #B15652\",\n\"@      c #C68380\",\n\"#      c #979797\",\n\"$      c #B3B3B3\",\n\"%      c #999999\",\n\"&      c #860600\",\n\"*      c #910600\",\n\"=      c #646464\",\n\"-      c #6C6C6C\",\n\";      c #5C5C5C\",\n\">      c #8B0600\",\n\",      c #960600\",\n\"'      c #6A6A6A\",\n\")      c #737373\",\n\"!      c #626262\",\n\"~      c #920600\",\n\"{      c #9E0700\",\n\"]      c #747474\",\n\"^      c #7D7D7D\",\n\"/      c #990600\",\n\"(      c #A50700\",\n\"_      c #7E7E7E\",\n\":      c #888888\",\n\"<      c #A10600\",\n\"[      c #AE0700\",\n\"}      c #939393\",\n\"|      c #A90700\",\n\"1      c #B70800\",\n\"2      c #9F9F9F\",\n\"3      c #B30700\",\n\"4      c #C10800\",\n\"5      c #9D9D9D\",\n\"6      c #AAAAAA\",\n\"7      c #919191\",\n\"8      c #BB0800\",\n\"9      c #CA0900\",\n\"0      c #A8A8A8\",\n\"a      c #B5B5B5\",\n\"b      c #9A9A9A\",\n\"c      c #C40800\",\n\"d      c #D40900\",\n\"e      c #B0B0B0\",\n\"f      c #BEBEBE\",\n\"g      c #A2A2A2\",\n\"h      c #CD0900\",\n\"i      c #DD0A00\",\n\"j      c #444444\",\n\"k      c #3A3A3A\",\n\"l      c #D50900\",\n\"m      c #DA0900\",\n\"n      c #C80800\",\n\"o      c #9C0700\",\n\"p      c #680500\",\n\"q      c #A00700\",\n\"r      c #CB0900\",\n\"s      c #3D0300\",\n\"t      c #490300\",\n\"u      c #A70800\",\n\"v      c #540400\",\n\"w      c #010000\",\n\"x      c #660400\",\n\"      .......   \",\n\" ......+@@@+... \",\n\" .#$%$.&***&.#. \",\n\" .=-;-.>,,,>.=. \",\n\" .')!).~{{{~.'. \",\n\" .]^'^./(((/.]. \",\n\" ._:]:.<[[[<._. \",\n\" .:}^}.|111|.:. \",\n\" .}2:2.34443.}. \",\n\" .5676.89998.5. \",\n\" .0aba.cdddc.0. \",\n\" .efgf.hiiih.e. \",\n\" .'jkj.lmmml.'. \",\n\" ......nopqr... \",\n\"      .qs.tu.   \",\n\"      .v. wx.   \"};\n\n/* XPM */\nstatic const char *const tools_s_xpm[] = {\n\"16 16 45 1\",\n\"  c #7B127D1C7D08\",\n\". c #82778EED8E15\",\n\"X c #8F3F975396FF\",\n\"o c #9A98A40CA38F\",\n\"O c #833D8EC28E02\",\n\"+ c #99C89EDF9EC2\",\n\"@ c #B347BD91BD42\",\n\"# c #7E4181A081AF\",\n\"$ c #8744915990DA\",\n\"% c #86728A968A7C\",\n\"& c #8D56924B9226\",\n\"* c #96DF9AC79AA6\",\n\"= c #9E01A473A438\",\n\"- c #AB41B55EB546\",\n\"; c #ABD1B155B117\",\n\": c #B5BFBC3DBC2F\",\n\"> c #B917BCECBCAB\",\n\", c #B558B98BB958\",\n\"< c #CEA2CFDBCFD4\",\n\"1 c #A699AC55ABF1\",\n\"2 c #C2A5C5A8C57A\",\n\"3 c #C8B2CA27CA1C\",\n\"4 c #D80FD952D955\",\n\"5 c #DF19E120E134\",\n\"6 c #EAC7E9ECE9FE\",\n\"7 c #F164EF95EF9D\",\n\"8 c #F6E0F5E6F61C\",\n\"9 c #F013EEAEEEBF\",\n\"0 c #487651675130\",\n\"q c #6E4D7BA97B06\",\n\"w c #88F190679051\",\n\"e c #92339C0E9BC1\",\n\"r c #A1BBAB0FAA98\",\n\"t c #A24AA7C4A7A7\",\n\"y c #86C7935E92DC\",\n\"u c #C8C4D721D725\",\n\"i c #D0E6D516D518\",\n\"p c #76A38450834A\",\n\"a c #6B4B7B507A6D\",\n\"s c #7C6D882B878D\",\n\"d c #88F395099487\",\n\"f c #DB34DCE9DCF5\",\n\"g c #5B05673F66CD\",\n\"h c #9402A0549FCC\",\n\"  c None\",\n/* pixels */\n\"                \",\n\"     gO+Oq      \",\n\"     p*45<Xg    \",\n\"     h.>885oa   \",\n\"      p.3883Xg  \",\n\"  $O   s$488;s  \",\n\" O>1p  e;588;a  \",\n\" O331sr;7778:s  \",\n\" O343,**3678;a  \",\n\" O,643;#%368:s  \",\n\"  t<64>= &374f  \",\n\"   t244>= &379ih\",\n\"    d@:;=& &475u\",\n\"      ssr-* *44r\",\n\"         u-%#*%0\",\n\"          feww0g\"\n};\n\n/* XPM */\nstatic const char *const new_s_xpm[] = {\n\"11 11 35 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #482929\",\n\"@      c #0F0808\",\n\"#      c #390606\",\n\"$      c #A04242\",\n\"%      c #925050\",\n\"&      c #0F0707\",\n\"*      c #0B0000\",\n\"=      c #500000\",\n\"-      c #8C0101\",\n\";      c #944444\",\n\">      c #221515\",\n\",      c #0A0000\",\n\"'      c #560000\",\n\")      c #A10909\",\n\"!      c #AE3636\",\n\"~      c #230000\",\n\"{      c #AE0000\",\n\"]      c #B40000\",\n\"^      c #180808\",\n\"/      c #B43535\",\n\"(      c #C40000\",\n\"_      c #960000\",\n\":      c #190606\",\n\"<      c #C02B2B\",\n\"[      c #E00000\",\n\"}      c #6B0000\",\n\"|      c #120000\",\n\"1      c #590000\",\n\"2      c #A30000\",\n\"3      c #680000\",\n\"4      c #0F0000\",\n\"5      c #350000\",\n\"6      c #100000\",\n\"  .     .  \",\n\" .+@   @+. \",\n\".#$%& &%$#.\",\n\" *=-;>;-=* \",\n\"  ,')!)',  \",\n\"   ~{]{~   \",\n\"  ^/(_(/^  \",\n\" :<[}|}[<: \",\n\".1234 4321.\",\n\" .56   65. \",\n\"  .     .  \"};\n\n/* XPM */\nstatic const char *const search_xpm[] = {\n\"14 16 11 1\",\n\"       c None\",\n\".      c #000000\",\n\"+      c #EEEEEE\",\n\"[      c #EE0000\",\n\"}      c #CC0000\",\n\"|      c #BB0000\",\n\"1      c #AA0000\",\n\"2      c #880000\",\n\"3      c #660000\",\n\"4      c #440000\",\n\"5      c #330000\",\n\"   .....      \",\n\"  .     .     \",\n\" .  +    .    \",\n\".  +++    .   \",\n\".   +     .   \",\n\".         .   \",\n\".         .   \",\n\" .       .    \",\n\"  .     .     \",\n\"   ...545     \",\n\"       424    \",\n\"       313    \",\n\"        2|2   \",\n\"        2}2   \",\n\"         2}2  \",\n\"         11   \"};\n\n/* XPM */\nstatic const char *const help_xpm[] = {\n\"14 16 16 1\",\n\"       c None\",\n\"1      c #DBDBDB\",\n\"2      c #B6B6B6\",\n\"3      c #929292\",\n\"4      c #6D6D6D\",\n\"5      c #F1EFEF\",\n\"6      c #018B00\",\n\"7      c #A48A53\",\n\"8      c #A4BDA4\",\n\"9      c #000000\",\n\"A      c #000000\",\n\"B      c #000000\",\n\"C      c #000000\",\n\"D      c #000000\",\n\"E      c #000000\",\n\"F      c #000000\",\n\"              \",\n\"    444333    \",\n\"   4   4384   \",\n\"  44    4283  \",\n\"  4444  4824  \",\n\"  4444  4274  \",\n\"   44  4274   \",\n\"       474    \",\n\"      474     \",\n\"      44      \",\n\"      44      \",\n\"              \",\n\"      44      \",\n\"     4764     \",\n\"     4674     \",\n\"      44      \"};\n\n/* XPM */\nstatic const char *const mini_bug_xpm[] = {\n\"16 16 7 1\",\n\"       c None\",\n\".      c #000000000000\",\n\"r      c #FFFF00000000\",\n\"X      c #BEFBC30BBEFB\",\n\"o      c #861782078617\",\n\"O      c #FFFFFFFFFFFF\",\n\"+      c #30C230C230C2\",\n\"                \",\n\"     .   .      \",\n\"      ...       \",\n\"    X.....X     \",\n\"    o.O...o     \",\n\"   o.O...o.o    \",\n\"   .rrrXrrr.    \",\n\"  .rrrrXrrrr.   \",\n\"   .rrrXrrr.    \",\n\"  .o.rrXrr.o.   \",\n\"    .rrXrr.     \",\n\"   .XrrXrrX.    \",\n\"      .o.       \",\n\"           .+++.\",\n\"            .o. \",\n\"             .  \"};\n\n/* XPM */\nstatic const char *const mini_ok_xpm[] = {\n\"15 15 5 1\",\n\"@ c #000000\",\n\"a c #808080\",\n\"b c #303030\",\n\"c c #606060\",\n\"  s none m none c none\",\n\"               \",\n\"               \",\n\"            @  \",\n\"           @@  \",\n\"          @@@  \",\n\"         @@@   \",\n\"   @@   @@@    \",\n\"  @@@  @@@     \",\n\"  @@@ @@@      \",\n\"  @@@@@@       \",\n\"  @@@@@        \",\n\"  @@@@         \",\n\"   @@     @bbb@\",\n\"           @a@ \",\n\"            @  \"\n};\n\n#if 0\n\n/* XPM */\nstatic const char *const left_i_xpm[] = {\n\"22 22 3 1\",\n\"  c None\",\n\". c #000000\",\n\"@ c gray70\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"        @@            \",\n\"       @@@            \",\n\"      @@@@            \",\n\"     @@@@@@@@@@ @@@@  \",\n\"    @@@@@@@@@@@ @@@@  \",\n\"   @@@@@@@@@@@@ @@@@  \",\n\"  @@@@@@@@@@@@@ @@@@  \",\n\"  @@@@@@@@@@@@@ @@@@  \",\n\"   @@@@@@@@@@@@ @@@@  \",\n\"    @@@@@@@@@@@ @@@@  \",\n\"     @@@@@@@@@@ @@@@  \",\n\"      @@@@            \",\n\"       @@@            \",\n\"        @@            \",\n\"                 @@@@@\",\n\"                  @ @ \",\n\"                   @  \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const right_i_xpm[] = {\n\"22 22 3 1\",\n\"  c None\",\n\". c #000000\",\n\"@ c gray70\",\n\"                      \",\n\"                      \",\n\"                      \",\n\"                      \",\n\"            @@        \",\n\"            @@@       \",\n\"            @@@@      \",\n\"  @@@@ @@@@@@@@@@     \",\n\"  @@@@ @@@@@@@@@@@    \",\n\"  @@@@ @@@@@@@@@@@@   \",\n\"  @@@@ @@@@@@@@@@@@@  \",\n\"  @@@@ @@@@@@@@@@@@@  \",\n\"  @@@@ @@@@@@@@@@@@   \",\n\"  @@@@ @@@@@@@@@@@    \",\n\"  @@@@ @@@@@@@@@@     \",\n\"            @@@@      \",\n\"            @@@       \",\n\"            @@        \",\n\"                 @@@@@\",\n\"                  @ @ \",\n\"                   @  \",\n\"                      \"};\n\n/* XPM */\nstatic const char *const stop_i_xpm[] = {\n/* columns rows colors chars-per-pixel */\n\"22 22 2 1\",\n\"       c None\",\n\"@      c gray70\",\n/* pixels */\n\"                      \",\n\"                      \",\n\"      @@@@@@@@@       \",\n\"     @@@@@@@@@@@      \",\n\"    @@@@@@@@@@@@@     \",\n\"   @@@@@@@@@@@@@@@    \",\n\"  @@@@@@@@@@@@@@@@@   \",\n\" @@@@@@@@@@@@@@@@@@@  \",\n\" @@@@@@  @@@  @@@@@@  \",\n\" @@@@@@   @   @@@@@@  \",\n\" @@@@@@@     @@@@@@@  \",\n\" @@@@@@@@   @@@@@@@@  \",\n\" @@@@@@@     @@@@@@@  \",\n\" @@@@@@   @   @@@@@@  \",\n\" @@@@@@  @@@  @@@@@@  \",\n\" @@@@@@@@@@@@@@@@@@@  \",\n\"  @@@@@@@@@@@@@@@@@   \",\n\"   @@@@@@@@@@@@@@@    \",\n\"    @@@@@@@@@@@@@     \",\n\"     @@@@@@@@@@@      \",\n\"      @@@@@@@@@       \",\n\"                      \"\n};\n\n/* XPM */\nstatic const char *const stop_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"    @@@@@@@@    \",\n\"   @@@@@@@@@@   \",\n\"  @@@@@@@@@@@@  \",\n\" @@@@@@@@@@@@@@ \",\n\"@@@@@@@@@@@@@@@@\",\n\"@@@@@ @@@@ @@@@@\",\n\"@@@@@@ @@ @@@@@@\",\n\"@@@@@@@  @@@@@@@\",\n\"@@@@@@@  @@@@@@@\",\n\"@@@@@@ @@ @@@@@@\",\n\"@@@@@ @@@@ @@@@@\",\n\"@@@@@@@@@@@@@@@@\",\n\" @@@@@@@@@@@@@@ \",\n\"  @@@@@@@@@@@@  \",\n\"   @@@@@@@@@@   \",\n\"    @@@@@@@@    \"};\n\n/* XPM */\nstatic const char *const left_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"                \",\n\"      @@        \",\n\"     @@@        \",\n\"    @@@@        \",\n\"   @@@@@@@@ @@@@\",\n\"  @@@@@@@@@ @@@@\",\n\" @@@@@@@@@@ @@@@\",\n\"@@@@@@@@@@@ @@@@\",\n\"@@@@@@@@@@@ @@@@\",\n\" @@@@@@@@@@ @@@@\",\n\"  @@@@@@@@@ @@@@\",\n\"   @@@@@@@@ @@@@\",\n\"    @@@@        \",\n\"     @@@   @@@@@\",\n\"      @@    @ @ \",\n\"             @  \"};\n\n/* XPM */\nstatic const char *const right_si_xpm[] = {\n\"16 16 2 1\",\n\"       c None\",\n\"@      c gray70\",\n\"                \",\n\"        @@      \",\n\"        @@@     \",\n\"        @@@@    \",\n\"@@@@ @@@@@@@@   \",\n\"@@@@ @@@@@@@@@  \",\n\"@@@@ @@@@@@@@@@ \",\n\"@@@@ @@@@@@@@@@@\",\n\"@@@@ @@@@@@@@@@@\",\n\"@@@@ @@@@@@@@@@ \",\n\"@@@@ @@@@@@@@@  \",\n\"@@@@ @@@@@@@@   \",\n\"        @@@@    \",\n\"        @@@@@@@@\",\n\"        @@  @ @ \",\n\"             @  \"};\n#endif\n\n#endif /* __PIXMAPS_H__ */\n"
  },
  {
    "path": "src/plain.cc",
    "content": "/*\n * File: plain.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Module for decoding a text/plain object into a dw widget.\n */\n\n#include \"msg.h\"\n#include \"prefs.h\"\n#include \"cache.h\"\n#include \"bw.h\"\n#include \"web.hh\"\n#include \"misc.h\"\n#include \"styleengine.hh\"\n\n#include \"uicmd.hh\"\n\n#include \"dw/core.hh\"\n#include \"dw/textblock.hh\"\n\n// Dw to Textblock\n#define DW2TB(dw)  ((Textblock*)dw)\n\nusing namespace dw;\nusing namespace dw::core;\n\n\nclass DilloPlain {\nprivate:\n   class PlainLinkReceiver: public dw::core::Layout::LinkReceiver {\n   public:\n      DilloPlain *plain;\n      bool press(dw::core::Widget *widget, int link, int img, int x, int y,\n                 dw::core::EventButton *event);\n   };\n   PlainLinkReceiver plainReceiver;\n\n   void addLine(char *Buf, uint_t BufSize);\n\npublic:\n   BrowserWindow *bw;\n\n   Widget *dw;\n   style::Style *widgetStyle;\n   size_t Start_Ofs;    /* Offset of where to start reading next */\n   int state;\n\n   DilloPlain(BrowserWindow *bw);\n   ~DilloPlain();\n\n   void write(void *Buf, uint_t BufSize, int Eof);\n};\n\n/* FSM states */\nenum {\n   ST_SeekingEol,\n   ST_Eol,\n   ST_Eof\n};\n\n/*\n * Exported function with C linkage.\n */\nextern \"C\" {\nvoid *a_Plain_text(const char *type, void *P, CA_Callback_t *Call,void **Data);\n}\n\n/*\n * Forward declarations\n */\nstatic void Plain_callback(int Op, CacheClient_t *Client);\nvoid a_Plain_free(void *data);\n\n\n/*\n * Diplain constructor.\n */\nDilloPlain::DilloPlain(BrowserWindow *p_bw)\n{\n   /* Init event receiver */\n   plainReceiver.plain = this;\n\n   /* Init internal variables */\n   bw = p_bw;\n   dw = new Textblock (prefs.limit_text_width);\n   Start_Ofs = 0;\n   state = ST_SeekingEol;\n\n   Layout *layout = (Layout*) bw->render_layout;\n   // TODO (1x) No URL?\n   StyleEngine styleEngine (layout, NULL, NULL);\n\n   styleEngine.startElement (\"body\", bw);\n   styleEngine.startElement (\"pre\", bw);\n   widgetStyle = styleEngine.wordStyle (bw);\n   widgetStyle->ref ();\n\n   /* The context menu */\n   layout->connectLink (&plainReceiver);\n\n   /* Hook destructor to the dw delete call */\n   dw->setDeleteCallback(a_Plain_free, this);\n}\n\n/*\n * Free memory used by the DilloPlain class.\n */\nDilloPlain::~DilloPlain()\n{\n   _MSG(\"::~DilloPlain()\\n\");\n   widgetStyle->unref();\n}\n\n/*\n * Receive the mouse button press event\n */\nbool DilloPlain::PlainLinkReceiver::press (Widget *widget, int, int, int, int,\n                                           EventButton *event)\n{\n   _MSG(\"DilloPlain::PlainLinkReceiver::buttonPress\\n\");\n\n   if (event->button == 3) {\n      a_UIcmd_page_popup(plain->bw, FALSE, NULL);\n      return true;\n   }\n   return false;\n}\n\nvoid DilloPlain::addLine(char *Buf, uint_t BufSize)\n{\n   int len;\n   char buf[129];\n   char *end = Buf + BufSize;\n\n   if (BufSize > 0) {\n      // Limit word length to avoid X11 coordinate\n      // overflow with extremely long lines.\n      while ((len = a_Misc_expand_tabs(&Buf, end, buf, sizeof(buf) - 1))) {\n         assert ((uint_t)len < sizeof(buf));\n         buf[len] = '\\0';\n         DW2TB(dw)->addText(buf, len, widgetStyle);\n      }\n   } else {\n      // Add dummy word for empty lines - otherwise the parbreak is ignored.\n      DW2TB(dw)->addText(\"\", 0, widgetStyle);\n   }\n\n   DW2TB(dw)->addParbreak(0, widgetStyle);\n}\n\n/*\n * Here we parse plain text and put it into the page structure.\n * (This function is called by Plain_callback whenever there's new data)\n */\nvoid DilloPlain::write(void *Buf, uint_t BufSize, int Eof)\n{\n   char *Start;\n   uint_t i, len, MaxBytes;\n\n   _MSG(\"DilloPlain::write Eof=%d\\n\", Eof);\n\n   Start = (char*)Buf + Start_Ofs;\n   MaxBytes = BufSize - Start_Ofs;\n   i = len = 0;\n   while ( i < MaxBytes ) {\n      switch ( state ) {\n      case ST_SeekingEol:\n         if (Start[i] == '\\n' || Start[i] == '\\r')\n            state = ST_Eol;\n         else {\n            ++i; ++len;\n         }\n         break;\n      case ST_Eol:\n         addLine(Start + i - len, len);\n         if (Start[i] == '\\r' && Start[i + 1] == '\\n') ++i;\n         if (i < MaxBytes) ++i;\n         state = ST_SeekingEol;\n         len = 0;\n         break;\n      }\n   }\n   Start_Ofs += i - len;\n   if (Eof && len) {\n      addLine(Start + i - len, len);\n      Start_Ofs += len;\n   }\n\n   DW2TB(dw)->flush();\n}\n\n/*\n * Set callback function and callback data for \"text/\" MIME major-type.\n */\nvoid *a_Plain_text(const char *type, void *P, CA_Callback_t *Call, void **Data)\n{\n   DilloWeb *web = (DilloWeb*)P;\n   DilloPlain *plain = new DilloPlain(web->bw);\n\n   *Call = (CA_Callback_t)Plain_callback;\n   *Data = (void*)plain;\n\n   return (void*)plain->dw;\n}\n\nvoid a_Plain_free(void *data)\n{\n   _MSG(\"a_Plain_free! %p\\n\", data);\n   delete ((DilloPlain *)data);\n}\n\n/*\n * This function is a cache client\n */\nstatic void Plain_callback(int Op, CacheClient_t *Client)\n{\n   DilloPlain *plain = (DilloPlain*)Client->CbData;\n\n   if (Op) {\n      /* Do the last line: */\n      plain->write(Client->Buf, Client->BufSize, 1);\n      /* remove this client from our active list */\n      a_Bw_close_client(plain->bw, Client->Key);\n   } else {\n      plain->write(Client->Buf, Client->BufSize, 0);\n   }\n}\n\n"
  },
  {
    "path": "src/png.c",
    "content": "/*\n * The png decoder for Dillo. It is responsible for decoding PNG data\n * and transferring it to the dicache.\n *\n * Geoff Lane nov 1999 zzassgl@twirl.mcc.ac.uk\n * Luca Rota, Jorge Arellano Cid, Eric Gaudet 2000\n * Jorge Arellano Cid 2009\n *\n * \"PNG: The Definitive Guide\" by Greg Roelofs, O'Reilly\n * ISBN 1-56592-542-4\n */\n\n#include <config.h>\n#ifdef ENABLE_PNG\n\n#include <stdlib.h> /* For abort() */\n\n#ifdef HAVE_LIBPNG_PNG_H\n#include <libpng/png.h>\n#else\n#include <png.h>\n#endif\n\n#include \"msg.h\"\n#include \"image.hh\"\n#include \"cache.h\"\n#include \"dicache.h\"\n\nenum prog_state {\n   IS_finished, IS_init, IS_nextdata\n};\n\n#if 0\nstatic char *prog_state_name[] =\n{\n   \"IS_finished\", \"IS_init\", \"IS_nextdata\"\n};\n#endif\n\n/*\n * This holds the data that must be saved between calls to this module.\n * Each time it is called it is supplied with a vector of data bytes\n * obtained from the web server. The module can process any amount of the\n * supplied data.  The next time the module is called, the vector may be\n * extended with additional data bytes to be processed.  The module must\n * keep track of the current start and cursor position of the input data\n * vector.  As complete output rasters are determined they are sent out of\n * the module for additional processing.\n *\n * NOTE:  There is no external control of the splitting of the input data\n * vector (only this module understands PNG format data.) This means that\n * the complete state of a PNG image being processed must be held in the\n * structure below so that processing can be suspended or resumed at any\n * point within an input image.\n *\n * In the case of the libpng library, it maintains its own state in\n * png_ptr and into_ptr so the FSM is very simple - much simpler than the\n * ones for XBM and PNM are.\n */\n\ntypedef struct {\n   DilloImage *Image;           /* Image meta data */\n   DilloUrl *url;               /* Primary Key for the dicache */\n   int version;                 /* Secondary Key for the dicache */\n   int bgcolor;                 /* Parent widget background color */\n\n   png_uint_32 width;           /* png image width */\n   png_uint_32 height;          /* png image height */\n   png_structp png_ptr;         /* libpng private data */\n   png_infop info_ptr;          /* libpng private info */\n   uchar_t *image_data;         /* decoded image data    */\n   uchar_t **row_pointers;      /* pntr to row starts    */\n   jmp_buf jmpbuf;              /* png error processing */\n   int error;                   /* error flag */\n   png_uint_32 previous_row;\n   int rowbytes;                /* No. bytes in image row */\n   short channels;              /* No. image channels */\n\n/*\n * 0                                              last byte\n * +-------+-+-----------------------------------+-+\n * |       | |     -- data to be processed --    | |\n * +-------+-+-----------------------------------+-+\n * ^        ^                                     ^\n * ipbuf    ipbufstart                            ipbufsize\n */\n\n   uchar_t *ipbuf;              /* image data in buffer */\n   int ipbufstart;              /* first valid image byte */\n   int ipbufsize;               /* size of valid data in */\n\n   enum prog_state state;       /* FSM current state  */\n\n   uchar_t *linebuf;            /* o/p raster data */\n\n} DilloPng;\n\n#define DATASIZE  (png->ipbufsize - png->ipbufstart)\n\n\nstatic\nvoid Png_error_handling(png_structp png_ptr, png_const_charp msg)\n{\n   DilloPng *png;\n\n   png = png_get_error_ptr(png_ptr);\n   MSG(\"Png_error_handling: %s: %s\\n\", URL_STR(png->url), msg);\n\n   png->error = 1;\n   png->state = IS_finished;\n\n   longjmp(png->jmpbuf, 1);\n}\n\nstatic void\nPng_datainfo_callback(png_structp png_ptr, png_infop info_ptr)\n{\n   DilloPng *png;\n   int color_type;\n   int bit_depth;\n   int interlace_type;\n   uint_t i;\n   double file_gamma = 1 / 2.2;\n\n   _MSG(\"Png_datainfo_callback:\\n\");\n\n   png = png_get_progressive_ptr(png_ptr);\n   dReturn_if_fail (png != NULL);\n\n   png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height,\n                &bit_depth, &color_type, &interlace_type, NULL, NULL);\n\n   /* check max image size */\n   if (png->width == 0 || png->height == 0 ||\n       png->width > IMAGE_MAX_AREA / png->height) {\n      MSG(\"Png_datainfo_callback: suspicious image size request %lu x %lu\\n\",\n          (ulong_t) png->width, (ulong_t) png->height);\n      Png_error_handling(png_ptr, \"Aborting...\");\n      return; /* not reached */\n   }\n\n   _MSG(\"Png_datainfo_callback: png->width  = %lu\\n\"\n        \"Png_datainfo_callback: png->height = %lu\\n\",\n        (ulong_t) png->width, (ulong_t) png->height);\n\n   /* we need RGB/RGBA in the end */\n   if (color_type == PNG_COLOR_TYPE_PALETTE && bit_depth <= 8) {\n      /* Convert indexed images to RGB */\n      png_set_expand (png_ptr);\n   } else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) {\n      /* Convert grayscale to RGB */\n      png_set_expand (png_ptr);\n   } else if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS)) {\n      /* We have transparency header, convert it to alpha channel */\n      png_set_expand(png_ptr);\n   } else if (bit_depth < 8) {\n      png_set_expand(png_ptr);\n   }\n\n   if (bit_depth == 16) {\n      png_set_strip_16(png_ptr);\n   }\n\n   /* Get and set gamma information. Beware: gamma correction 2.2 will\n      only work on PC's. TODO: select screen gamma correction for other\n      platforms. */\n   if (png_get_gAMA(png_ptr, info_ptr, &file_gamma))\n      png_set_gamma(png_ptr, 2.2, file_gamma);\n\n   /* Convert gray scale to RGB */\n   if (color_type == PNG_COLOR_TYPE_GRAY ||\n       color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {\n      png_set_gray_to_rgb(png_ptr);\n   }\n\n   /* Interlaced */\n   if (interlace_type != PNG_INTERLACE_NONE) {\n      png_set_interlace_handling(png_ptr);\n   }\n\n   /* get libpng to update its state */\n   png_read_update_info(png_ptr, info_ptr);\n\n   png_get_IHDR(png_ptr, info_ptr, &png->width, &png->height,\n                &bit_depth, &color_type, &interlace_type, NULL, NULL);\n\n   png->rowbytes = png_get_rowbytes(png_ptr, info_ptr);\n   png->channels = png_get_channels(png_ptr, info_ptr);\n\n   /* init Dillo specifics */\n   _MSG(\"Png_datainfo_callback: rowbytes = %d\\n\"\n        \"Png_datainfo_callback: width    = %lu\\n\"\n        \"Png_datainfo_callback: height   = %lu\\n\",\n        png->rowbytes, (ulong_t) png->width, (ulong_t) png->height);\n\n   png->image_data = (uchar_t *) dMalloc(png->rowbytes * png->height);\n   png->row_pointers = (uchar_t **) dMalloc(png->height * sizeof(uchar_t *));\n\n   for (i = 0; i < png->height; i++)\n      png->row_pointers[i] = png->image_data + (i * png->rowbytes);\n\n   png->linebuf = dMalloc(3 * png->width);\n\n   /* Initialize the dicache-entry here */\n   a_Dicache_set_parms(png->url, png->version, png->Image,\n                       (uint_t)png->width, (uint_t)png->height,\n                       DILLO_IMG_TYPE_RGB, file_gamma);\n   png->Image = NULL; /* safeguard: hereafter it may be freed by its owner */\n}\n\nstatic void\n Png_datarow_callback(png_structp png_ptr, png_bytep new_row,\n                      png_uint_32 row_num, int pass)\n{\n   DilloPng *png;\n   uint_t i;\n\n   if (!new_row)                /* work to do? */\n      return;\n\n   _MSG(\"Png_datarow_callback: row_num = %ld\\n\", row_num);\n\n   png = png_get_progressive_ptr(png_ptr);\n\n   png_progressive_combine_row(png_ptr, png->row_pointers[row_num], new_row);\n\n   _MSG(\"png: row_num=%u previous_row=%u\\n\", row_num, png->previous_row);\n   if (row_num < png->previous_row) {\n      a_Dicache_new_scan(png->url, png->version);\n   }\n   png->previous_row = row_num;\n\n   switch (png->channels) {\n   case 3:\n      a_Dicache_write(png->url, png->version,\n                      png->image_data + (row_num * png->rowbytes),\n                      (uint_t)row_num);\n      break;\n   case 4:\n     {\n        /* TODO: get the backgound color from the parent\n         * of the image widget -- Livio.                 */\n        int a, bg_red, bg_green, bg_blue;\n        uchar_t *pl = png->linebuf;\n        uchar_t *data = png->image_data + (row_num * png->rowbytes);\n\n        /* TODO: maybe change prefs.bg_color to `a_Dw_widget_get_bg_color`,\n         * when background colors are correctly implementated */\n        bg_blue  = (png->bgcolor) & 0xFF;\n        bg_green = (png->bgcolor>>8) & 0xFF;\n        bg_red   = (png->bgcolor>>16) & 0xFF;\n\n        for (i = 0; i < png->width; i++) {\n           a = *(data+3);\n\n           if (a == 255) {\n              *(pl++) = *(data++);\n              *(pl++) = *(data++);\n              *(pl++) = *(data++);\n              data++;\n           } else if (a == 0) {\n              *(pl++) = bg_red;\n              *(pl++) = bg_green;\n              *(pl++) = bg_blue;\n              data += 4;\n           } else {\n              png_composite(*(pl++), *(data++), a, bg_red);\n              png_composite(*(pl++), *(data++), a, bg_green);\n              png_composite(*(pl++), *(data++), a, bg_blue);\n              data++;\n           }\n        }\n        a_Dicache_write(png->url, png->version, png->linebuf, (uint_t)row_num);\n        break;\n     }\n   default:\n      MSG(\"Png_datarow_callback: unexpected number of channels=%d pass=%d\\n\",\n          png->channels, pass);\n      abort();\n   }\n}\n\nstatic void Png_dataend_callback(png_structp png_ptr, png_infop info_ptr)\n{\n   DilloPng *png;\n\n   _MSG(\"Png_dataend_callback:\\n\");\n   if (!info_ptr)\n      MSG(\"Png_dataend_callback: info_ptr = NULL\\n\");\n\n   png = png_get_progressive_ptr(png_ptr);\n   png->state = IS_finished;\n}\n\n/*\n * Free up the resources for this image.\n */\nstatic void Png_free(DilloPng *png)\n{\n   _MSG(\"Png_free: png=%p\\n\", png);\n\n   dFree(png->image_data);\n   dFree(png->row_pointers);\n   dFree(png->linebuf);\n   if (setjmp(png->jmpbuf))\n      MSG_WARN(\"PNG: can't destroy read structure\\n\");\n   else if (png->png_ptr)\n      png_destroy_read_struct(&png->png_ptr, &png->info_ptr, NULL);\n   dFree(png);\n}\n\n/*\n * Finish the decoding process (and free the memory)\n */\nstatic void Png_close(DilloPng *png, CacheClient_t *Client)\n{\n   _MSG(\"Png_close\\n\");\n   /* Let dicache know decoding is over */\n   a_Dicache_close(png->url, png->version, Client);\n   Png_free(png);\n}\n\n/*\n * Receive and process new chunks of PNG image data\n */\nstatic void Png_write(DilloPng *png, void *Buf, uint_t BufSize)\n{\n   dReturn_if_fail ( Buf != NULL && BufSize > 0 );\n\n   /* Keep local copies so we don't have to pass multiple args to\n    * a number of functions. */\n   png->ipbuf = Buf;\n   png->ipbufsize = BufSize;\n\n   /* start/resume the FSM here */\n   while (png->state != IS_finished && DATASIZE) {\n      _MSG(\"State = %s\\n\", prog_state_name[png->state]);\n\n      switch (png->state) {\n      case IS_init:\n         if (DATASIZE < 8) {\n            return;            /* need MORE data */\n         }\n         /* check the image signature - DON'T update ipbufstart! */\n         if (png_sig_cmp(png->ipbuf, 0, DATASIZE)) {\n            MSG_WARN(\"\\\"%s\\\" is not a PNG file.\\n\", URL_STR(png->url));\n            png->state = IS_finished;\n            break;\n         }\n         /* OK, it looks like a PNG image, lets do some set up stuff */\n         png->png_ptr = png_create_read_struct(\n                           PNG_LIBPNG_VER_STRING,\n                           png,\n                           (png_error_ptr)Png_error_handling,\n                           (png_error_ptr)Png_error_handling);\n         dReturn_if_fail (png->png_ptr != NULL);\n         png->info_ptr = png_create_info_struct(png->png_ptr);\n         dReturn_if_fail (png->info_ptr != NULL);\n\n         setjmp(png->jmpbuf);\n         if (!png->error) {\n            png_set_progressive_read_fn(\n               png->png_ptr,\n               png,\n               Png_datainfo_callback,   /* performs local init functions */\n               Png_datarow_callback,    /* performs per row action */\n               Png_dataend_callback);   /* performs cleanup actions */\n            png->state = IS_nextdata;\n         }\n         break;\n\n      case IS_nextdata:\n         if (setjmp(png->jmpbuf)) {\n            png->state = IS_finished;\n         } else if (!png->error) {\n            png_process_data( png->png_ptr,\n                              png->info_ptr,\n                              png->ipbuf + png->ipbufstart,\n                              (png_size_t)DATASIZE);\n\n            png->ipbufstart += DATASIZE;\n         }\n         break;\n\n      default:\n         MSG_WARN(\"PNG decoder: bad state = %d\\n\", png->state);\n         abort();\n      }\n   }\n}\n\n/*\n * Op:  Operation to perform.\n *   If (Op == 0)\n *      start or continue processing an image if image data exists.\n *   else\n *       terminate processing, cleanup any allocated memory,\n *       close down the decoding process.\n *\n * Client->CbData  : pointer to previously allocated DilloPng work area.\n *  This holds the current state of the image processing and is kept\n *  across calls to this routine.\n * Client->Buf     : Pointer to data start.\n * Client->BufSize : the size of the data buffer.\n *\n * You have to keep track of where you are in the image data and\n * how much has been processed.\n *\n * It's entirely possible that you will not see the end of the data.  The\n * user may terminate transfer via a Stop button or there may be a network\n * failure.  This means that you can't just wait for all the data to be\n * presented before starting conversion and display.\n */\nvoid a_Png_callback(int Op, void *data)\n{\n   if (Op == CA_Send) {\n      CacheClient_t *Client = data;\n      Png_write(Client->CbData, Client->Buf, Client->BufSize);\n   } else if (Op == CA_Close) {\n      CacheClient_t *Client = data;\n      Png_close(Client->CbData, Client);\n   } else if (Op == CA_Abort) {\n      Png_free(data);\n   }\n}\n\n/*\n * Create the image state data that must be kept between calls\n */\nvoid *a_Png_new(DilloImage *Image, DilloUrl *url, int version)\n{\n   DilloPng *png = dNew0(DilloPng, 1);\n   _MSG(\"a_Png_new: png=%p\\n\", png);\n\n   png->Image = Image;\n   png->url = url;\n   png->version = version;\n   png->bgcolor = Image->bg_color;\n   png->error = 0;\n   png->ipbuf = NULL;\n   png->ipbufstart = 0;\n   png->ipbufsize = 0;\n   png->state = IS_init;\n   png->linebuf = NULL;\n   png->image_data = NULL;\n   png->row_pointers = NULL;\n   png->previous_row = 0;\n\n   return png;\n}\n\n#else /* ENABLE_PNG */\n\nvoid *a_Png_new() { return 0; }\nvoid a_Png_callback() { return; }\n\n#endif /* ENABLE_PNG */\n"
  },
  {
    "path": "src/prefs.c",
    "content": "/*\n * Preferences\n *\n * Copyright (C) 2006-2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include \"prefs.h\"\n\n#define PREFS_START_PAGE      \"about:splash\"\n#define PREFS_HOME            \"https://dillo-browser.github.io/old/\"\n#define PREFS_FONT_SERIF      \"DejaVu Serif\"\n#define PREFS_FONT_SANS_SERIF \"DejaVu Sans\"\n#define PREFS_FONT_CURSIVE    \"URW Chancery L\"\n#define PREFS_FONT_FANTASY    \"DejaVu Sans\" /* TODO: find good default */\n#define PREFS_FONT_MONOSPACE  \"DejaVu Sans Mono\"\n#define PREFS_SEARCH_URL      \"dd http://duckduckgo.com/lite/?kp=-1&q=%s\"\n#define PREFS_NO_PROXY        \"localhost 127.0.0.1\"\n#define PREFS_SAVE_DIR        \"/tmp/\"\n#define PREFS_HTTP_REFERER    \"host\"\n#define PREFS_HTTP_USER_AGENT \"Dillo/\" VERSION\n#define PREFS_THEME           \"none\"\n#define PREFS_MEDIA_PLAYER           \"mpv -ytdl-format='bestvideo[height<=?360]+bestaudio/best'\"\n\n/*-----------------------------------------------------------------------------\n * Global Data\n *---------------------------------------------------------------------------*/\nDilloPrefs prefs;\n\n/*\n * Sets the default settings.\n */\n\nvoid a_Prefs_init(void)\n{\n   prefs.allow_white_bg = TRUE;\n   prefs.white_bg_replacement = 0xe0e0a3; // 0xdcd1ba;\n   prefs.bg_color = 0xFFFFFF;\n   prefs.buffered_drawing = 1;\n   prefs.contrast_visited_color = TRUE;\n   prefs.enterpress_forces_submit = FALSE;\n   prefs.focus_new_tab = TRUE;\n   prefs.font_cursive = dStrdup(PREFS_FONT_CURSIVE);\n   prefs.font_factor = 1.0;\n   prefs.font_max_size = 100;\n   prefs.font_min_size = 6;\n   prefs.font_fantasy = dStrdup(PREFS_FONT_FANTASY);\n   prefs.font_monospace = dStrdup(PREFS_FONT_MONOSPACE);\n   prefs.font_sans_serif = dStrdup(PREFS_FONT_SANS_SERIF);\n   prefs.font_serif = dStrdup(PREFS_FONT_SERIF);\n   prefs.fullwindow_start = FALSE;\n\n   /* these four constitute the geometry */\n   prefs.width = PREFS_GEOMETRY_DEFAULT_WIDTH;\n   prefs.height = PREFS_GEOMETRY_DEFAULT_HEIGHT;\n   prefs.xpos = PREFS_GEOMETRY_DEFAULT_XPOS;\n   prefs.ypos = PREFS_GEOMETRY_DEFAULT_YPOS;\n\n   prefs.home = a_Url_new(PREFS_HOME, NULL);\n   prefs.http_language = NULL;\n   prefs.http_proxy = NULL;\n   prefs.http_max_conns = 6;\n   prefs.http_persistent_conns = TRUE;\n   prefs.http_proxyuser = NULL;\n   prefs.http_referer = dStrdup(PREFS_HTTP_REFERER);\n   prefs.http_strict_transport_security = TRUE;\n   prefs.http_user_agent = dStrdup(PREFS_HTTP_USER_AGENT);\n   prefs.media_player = dStrdup(PREFS_MEDIA_PLAYER);\n   prefs.limit_text_width = FALSE;\n   prefs.adjust_min_width = TRUE;\n   prefs.adjust_table_min_width = TRUE;\n   prefs.load_images=TRUE;\n   prefs.load_background_images=FALSE;\n   prefs.use_cookies=TRUE;\n   prefs.load_stylesheets=TRUE;\n   prefs.middle_click_drags_page = TRUE;\n   prefs.middle_click_opens_new_tab = TRUE;\n   prefs.right_click_closes_tab = FALSE;\n   prefs.no_proxy = dStrdup(PREFS_NO_PROXY);\n   prefs.panel_size = P_medium;\n   prefs.parse_embedded_css=TRUE;\n   prefs.load_reader_mode_css=FALSE;\n   prefs.save_dir = dStrdup(PREFS_SAVE_DIR);\n   prefs.search_urls = dList_new(16);\n   dList_append(prefs.search_urls, dStrdup(PREFS_SEARCH_URL));\n   prefs.search_url_idx = 0;\n   prefs.show_back = TRUE;\n   prefs.show_bookmarks = TRUE;\n   prefs.show_clear_url = TRUE;\n   prefs.show_extra_warnings = FALSE;\n   prefs.show_filemenu=TRUE;\n   prefs.show_forw = TRUE;\n   prefs.show_help = TRUE;\n   prefs.show_home = TRUE;\n   prefs.show_msg = TRUE;\n   prefs.show_progress_box = TRUE;\n   prefs.show_quit_dialog = TRUE;\n   prefs.show_reload = TRUE;\n   prefs.show_save = TRUE;\n   prefs.show_url = TRUE;\n   prefs.show_search = TRUE;\n   prefs.show_stop = TRUE;\n   prefs.show_tools = TRUE;\n   prefs.show_tooltip = TRUE;\n   prefs.show_ui_tooltip = TRUE;\n   prefs.small_icons = FALSE;\n   prefs.start_page = a_Url_new(PREFS_START_PAGE, NULL);\n   prefs.theme = dStrdup(PREFS_THEME);\n   prefs.ui_button_highlight_color = -1;\n   prefs.ui_fg_color = -1;\n   prefs.ui_main_bg_color = -1;\n   prefs.ui_selection_color = -1;\n   prefs.ui_tab_active_bg_color = -1;\n   prefs.ui_tab_bg_color = -1;\n   prefs.ui_tab_active_fg_color = -1;\n   prefs.ui_tab_fg_color = -1;\n   prefs.ui_text_bg_color = -1;\n\n   prefs.penalty_hyphen = 100;\n   prefs.penalty_hyphen_2 = 800;\n   prefs.penalty_em_dash_left = 800;\n   prefs.penalty_em_dash_right = 100;\n   prefs.penalty_em_dash_right_2 = 800;\n   prefs.stretchability_factor = 100;\n}\n\n/*\n *  memory-deallocation\n *  (Call this one at exit time)\n */\nvoid a_Prefs_freeall(void)\n{\n   int i;\n\n   dFree(prefs.font_cursive);\n   dFree(prefs.font_fantasy);\n   dFree(prefs.font_monospace);\n   dFree(prefs.font_sans_serif);\n   dFree(prefs.font_serif);\n   a_Url_free(prefs.home);\n   dFree(prefs.http_language);\n   a_Url_free(prefs.http_proxy);\n   dFree(prefs.http_proxyuser);\n   dFree(prefs.http_referer);\n   dFree(prefs.http_user_agent);\n   dFree(prefs.media_player);\n   dFree(prefs.no_proxy);\n   dFree(prefs.save_dir);\n   for (i = 0; i < dList_length(prefs.search_urls); ++i)\n      dFree(dList_nth_data(prefs.search_urls, i));\n   dList_free(prefs.search_urls);\n   a_Url_free(prefs.start_page);\n   dFree(prefs.theme);\n}\n"
  },
  {
    "path": "src/prefs.h",
    "content": "/*\n * Preferences\n *\n * Copyright (C) 2006-2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef __PREFS_H__\n#define __PREFS_H__\n\n#include \"url.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n#define PREFS_GEOMETRY_DEFAULT_WIDTH   780\n#define PREFS_GEOMETRY_DEFAULT_HEIGHT  580\n#define PREFS_GEOMETRY_DEFAULT_XPOS  -9999\n#define PREFS_GEOMETRY_DEFAULT_YPOS  -9999\n\n/* FLTK has free color indices from 16 to 31 */\n#define PREFS_UI_BUTTON_HIGHLIGHT_COLOR 16\n#define PREFS_UI_TAB_ACTIVE_BG_COLOR 17\n#define PREFS_UI_TAB_ACTIVE_FG_COLOR 18\n#define PREFS_UI_TAB_BG_COLOR 19\n#define PREFS_UI_TAB_FG_COLOR 20\n\n/* Panel sizes */\nenum { P_tiny = 0, P_small, P_medium };\n\ntypedef struct {\n   int width;\n   int height;\n   int xpos;\n   int ypos;\n   char *http_language;\n   int32_t http_max_conns;\n   DilloUrl *http_proxy;\n   char *http_proxyuser;\n   char *http_referer;\n   char *http_user_agent;\n   char *media_player;\n   char *no_proxy;\n   DilloUrl *start_page;\n   DilloUrl *home;\n   bool_t allow_white_bg;\n   int32_t white_bg_replacement;\n   int32_t bg_color;\n   int32_t ui_button_highlight_color;\n   int32_t ui_fg_color;\n   int32_t ui_main_bg_color;\n   int32_t ui_selection_color;\n   int32_t ui_tab_active_bg_color;\n   int32_t ui_tab_active_fg_color;\n   int32_t ui_tab_bg_color;\n   int32_t ui_tab_fg_color;\n   int32_t ui_text_bg_color;\n   bool_t contrast_visited_color;\n   bool_t show_tooltip;\n   bool_t show_ui_tooltip;\n   char *theme;\n   int panel_size;\n   bool_t small_icons;\n   bool_t limit_text_width;\n   bool_t adjust_min_width;\n   bool_t adjust_table_min_width;\n   bool_t focus_new_tab;\n   double font_factor;\n   int32_t font_max_size;\n   int32_t font_min_size;\n   bool_t show_back;\n   bool_t show_forw;\n   bool_t show_home;\n   bool_t show_reload;\n   bool_t show_save;\n   bool_t show_stop;\n   bool_t show_bookmarks;\n   bool_t show_tools;\n   bool_t show_filemenu;\n   bool_t show_clear_url;\n   bool_t show_url;\n   bool_t show_search;\n   bool_t show_help;\n   bool_t show_progress_box;\n   bool_t show_quit_dialog;\n   bool_t fullwindow_start;\n   bool_t load_images;\n   bool_t load_background_images;\n   bool_t use_cookies;\n   bool_t load_stylesheets;\n   bool_t parse_embedded_css;\n   bool_t load_reader_mode_css;\n   bool_t http_persistent_conns;\n   bool_t http_strict_transport_security;\n   int32_t buffered_drawing;\n   char *font_serif;\n   char *font_sans_serif;\n   char *font_cursive;\n   char *font_fantasy;\n   char *font_monospace;\n   bool_t enterpress_forces_submit;\n   bool_t middle_click_opens_new_tab;\n   bool_t right_click_closes_tab;\n   bool_t search_url_idx;\n   Dlist *search_urls;\n   char *save_dir;\n   bool_t show_msg;\n   bool_t show_extra_warnings;\n   bool_t middle_click_drags_page;\n   int penalty_hyphen, penalty_hyphen_2;\n   int penalty_em_dash_left, penalty_em_dash_right, penalty_em_dash_right_2;\n   int stretchability_factor;\n} DilloPrefs;\n\n/* Global Data */\nextern DilloPrefs prefs;\n\nvoid a_Prefs_init(void);\nvoid a_Prefs_freeall(void);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __PREFS_H__ */\n"
  },
  {
    "path": "src/prefsparser.cc",
    "content": "/*\n * Preferences parser\n *\n * Copyright (C) 2006-2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <sys/types.h>\n#include <stdlib.h>\n#include <locale.h>            /* for setlocale */\n#include <math.h>              /* for isinf */\n#include <limits.h>\n\n#include \"prefs.h\"\n#include \"misc.h\"\n#include \"msg.h\"\n#include \"colors.h\"\n\n#include \"prefsparser.hh\"\n\ntypedef enum {\n   PREFS_BOOL,\n   PREFS_COLOR,\n   PREFS_STRING,\n   PREFS_STRINGS,\n   PREFS_URL,\n   PREFS_INT32,\n   PREFS_DOUBLE,\n   PREFS_FRACTION_100,\n   PREFS_GEOMETRY,\n   PREFS_PANEL_SIZE\n} PrefType_t;\n\ntypedef struct {\n   const char *name;\n   void *pref;\n   PrefType_t type;\n   int count;\n} SymNode_t;\n\n/*\n * Parse a name/value pair and set preferences accordingly.\n */\nstatic int parseOption(char *name, char *value,\n                       SymNode_t *symbols, int n_symbols)\n{\n   SymNode_t *node;\n   int i;\n   int st;\n\n   node = NULL;\n   for (i = 0; i < n_symbols; i++) {\n      if (!strcmp(symbols[i].name, name)) {\n         node = & (symbols[i]);\n         break;\n      }\n   }\n\n   if (!node) {\n      MSG(\"prefs: {%s} is not a recognized token.\\n\", name);\n      return -1;\n   }\n\n   switch (node->type) {\n   case PREFS_BOOL:\n      *(bool_t *)node->pref = (!dStrAsciiCasecmp(value, \"yes\") ||\n                               !dStrAsciiCasecmp(value, \"true\"));\n      break;\n   case PREFS_COLOR:\n      *(int32_t *)node->pref = a_Color_parse(value, *(int32_t*)node->pref,&st);\n      if (st == 1)\n         MSG(\"prefs: Color '%s' not recognized.\\n\", value);\n      break;\n   case PREFS_STRING:\n      dFree(*(char **)node->pref);\n      *(char **)node->pref = dStrdup(value);\n      break;\n   case PREFS_STRINGS:\n   {\n      Dlist *lp = *(Dlist **)node->pref;\n      if (node->count == 0) {\n         /* override the default */\n         for (i = 0; i < dList_length(lp); i++) {\n            void *data = dList_nth_data(lp, i);\n            dList_remove(lp, data);\n            dFree(data);\n         }\n      }\n      dList_append(lp, dStrdup(value));\n      break;\n   }\n   case PREFS_URL:\n      a_Url_free(*(DilloUrl **)node->pref);\n      *(DilloUrl **)node->pref = a_Url_new(value, NULL);\n      break;\n   case PREFS_INT32:\n      *(int32_t *)node->pref = strtol(value, NULL, 10);\n      break;\n   case PREFS_DOUBLE:\n      *(double *)node->pref = strtod(value, NULL);\n      break;\n   case PREFS_FRACTION_100:\n      {\n         double d = strtod (value, NULL);\n         if (isinf(d)) {\n            if (d > 0)\n               *(int*)node->pref = INT_MAX;\n            else\n               *(int*)node->pref = INT_MIN;\n         } else\n            *(int*)node->pref = 100 * d;\n      }\n      break;\n   case PREFS_GEOMETRY:\n      a_Misc_parse_geometry(value, &prefs.xpos, &prefs.ypos,\n                            &prefs.width, &prefs.height);\n      break;\n   case PREFS_PANEL_SIZE:\n      if (!dStrAsciiCasecmp(value, \"tiny\"))\n         prefs.panel_size = P_tiny;\n      else if (!dStrAsciiCasecmp(value, \"small\"))\n         prefs.panel_size = P_small;\n      else /* default to \"medium\" */\n         prefs.panel_size = P_medium;\n      break;\n   default:\n      MSG_WARN(\"prefs: {%s} IS recognized but not handled!\\n\", name);\n      break;   /* Not reached */\n   }\n   node->count++;\n\n   return 0;\n}\n\n/*\n * Parses the dillorc and sets the values in the prefs structure.\n */\nvoid PrefsParser::parse(FILE *fp)\n{\n   char *line, *name, *value, *oldLocale;\n   int st, line_number = 1;\n\n   /* Symbol array, sorted alphabetically */\n   static SymNode_t symbols[] = {\n      { \"allow_white_bg\", &prefs.allow_white_bg, PREFS_BOOL, 0 },\n      { \"white_bg_replacement\", &prefs.white_bg_replacement, PREFS_COLOR, 0 },\n      { \"bg_color\", &prefs.bg_color, PREFS_COLOR, 0 },\n      { \"buffered_drawing\", &prefs.buffered_drawing, PREFS_INT32, 0 },\n      { \"contrast_visited_color\", &prefs.contrast_visited_color, PREFS_BOOL, 0 },\n      { \"enterpress_forces_submit\", &prefs.enterpress_forces_submit,\n        PREFS_BOOL, 0 },\n      { \"focus_new_tab\", &prefs.focus_new_tab, PREFS_BOOL, 0 },\n      { \"font_cursive\", &prefs.font_cursive, PREFS_STRING, 0 },\n      { \"font_factor\", &prefs.font_factor, PREFS_DOUBLE, 0 },\n      { \"font_fantasy\", &prefs.font_fantasy, PREFS_STRING, 0 },\n      { \"font_max_size\", &prefs.font_max_size, PREFS_INT32, 0 },\n      { \"font_min_size\", &prefs.font_min_size, PREFS_INT32, 0 },\n      { \"font_monospace\", &prefs.font_monospace, PREFS_STRING, 0 },\n      { \"font_sans_serif\", &prefs.font_sans_serif, PREFS_STRING, 0 },\n      { \"font_serif\", &prefs.font_serif, PREFS_STRING, 0 },\n      { \"fullwindow_start\", &prefs.fullwindow_start, PREFS_BOOL, 0 },\n      { \"geometry\", NULL, PREFS_GEOMETRY, 0 },\n      { \"home\", &prefs.home, PREFS_URL, 0 },\n      { \"http_language\", &prefs.http_language, PREFS_STRING, 0 },\n      { \"http_max_conns\", &prefs.http_max_conns, PREFS_INT32, 0 },\n      { \"http_persistent_conns\", &prefs.http_persistent_conns, PREFS_BOOL, 0 },\n      { \"http_proxy\", &prefs.http_proxy, PREFS_URL, 0 },\n      { \"http_proxyuser\", &prefs.http_proxyuser, PREFS_STRING, 0 },\n      { \"http_referer\", &prefs.http_referer, PREFS_STRING, 0 },\n      { \"http_strict_transport_security\",&prefs.http_strict_transport_security,\n        PREFS_BOOL, 0 },\n      { \"http_user_agent\", &prefs.http_user_agent, PREFS_STRING, 0 },\n      { \"limit_text_width\", &prefs.limit_text_width, PREFS_BOOL, 0 },\n      { \"adjust_min_width\", &prefs.adjust_min_width, PREFS_BOOL, 0 },\n      { \"adjust_table_min_width\", &prefs.adjust_table_min_width, PREFS_BOOL, 0 },\n      { \"load_images\", &prefs.load_images, PREFS_BOOL, 0 },\n      { \"load_background_images\", &prefs.load_background_images, PREFS_BOOL, 0 },\n      { \"media_player\", &prefs.media_player, PREFS_STRING, 0 },\n      { \"use_cookies\", &prefs.use_cookies, PREFS_BOOL, 0 },\n      { \"load_stylesheets\", &prefs.load_stylesheets, PREFS_BOOL, 0 },\n      { \"load_reader_mode_css\", &prefs.load_reader_mode_css, PREFS_BOOL, 0 },\n      { \"middle_click_drags_page\", &prefs.middle_click_drags_page,\n        PREFS_BOOL, 0 },\n      { \"middle_click_opens_new_tab\", &prefs.middle_click_opens_new_tab,\n        PREFS_BOOL, 0 },\n      { \"right_click_closes_tab\", &prefs.right_click_closes_tab, PREFS_BOOL, 0 },\n      { \"no_proxy\", &prefs.no_proxy, PREFS_STRING, 0 },\n      { \"panel_size\", &prefs.panel_size, PREFS_PANEL_SIZE, 0 },\n      { \"parse_embedded_css\", &prefs.parse_embedded_css, PREFS_BOOL, 0 },\n      { \"save_dir\", &prefs.save_dir, PREFS_STRING, 0 },\n      { \"search_url\", &prefs.search_urls, PREFS_STRINGS, 0 },\n      { \"show_back\", &prefs.show_back, PREFS_BOOL, 0 },\n      { \"show_bookmarks\", &prefs.show_bookmarks, PREFS_BOOL, 0 },\n      { \"show_clear_url\", &prefs.show_clear_url, PREFS_BOOL, 0 },\n      { \"show_extra_warnings\", &prefs.show_extra_warnings, PREFS_BOOL, 0 },\n      { \"show_filemenu\", &prefs.show_filemenu, PREFS_BOOL, 0 },\n      { \"show_forw\", &prefs.show_forw, PREFS_BOOL, 0 },\n      { \"show_help\", &prefs.show_help, PREFS_BOOL, 0 },\n      { \"show_home\", &prefs.show_home, PREFS_BOOL, 0 },\n      { \"show_msg\", &prefs.show_msg, PREFS_BOOL, 0 },\n      { \"show_progress_box\", &prefs.show_progress_box, PREFS_BOOL, 0 },\n      { \"show_quit_dialog\", &prefs.show_quit_dialog, PREFS_BOOL, 0 },\n      { \"show_reload\", &prefs.show_reload, PREFS_BOOL, 0 },\n      { \"show_save\", &prefs.show_save, PREFS_BOOL, 0 },\n      { \"show_url\", &prefs.show_url, PREFS_BOOL, 0 },\n      { \"show_search\", &prefs.show_search, PREFS_BOOL, 0 },\n      { \"show_stop\", &prefs.show_stop, PREFS_BOOL, 0 },\n      { \"show_tools\", &prefs.show_tools, PREFS_BOOL, 0 },\n      { \"show_tooltip\", &prefs.show_tooltip, PREFS_BOOL, 0 },\n      { \"show_ui_tooltip\", &prefs.show_ui_tooltip, PREFS_BOOL, 0 },\n      { \"small_icons\", &prefs.small_icons, PREFS_BOOL, 0 },\n      { \"start_page\", &prefs.start_page, PREFS_URL, 0 },\n      { \"theme\", &prefs.theme, PREFS_STRING, 0 },\n      { \"ui_button_highlight_color\", &prefs.ui_button_highlight_color,\n        PREFS_COLOR, 0 },\n      { \"ui_fg_color\", &prefs.ui_fg_color, PREFS_COLOR, 0 },\n      { \"ui_main_bg_color\", &prefs.ui_main_bg_color, PREFS_COLOR, 0 },\n      { \"ui_selection_color\", &prefs.ui_selection_color, PREFS_COLOR, 0 },\n      { \"ui_tab_active_bg_color\", &prefs.ui_tab_active_bg_color, PREFS_COLOR, 0 },\n      { \"ui_tab_bg_color\", &prefs.ui_tab_bg_color, PREFS_COLOR, 0 },\n      { \"ui_tab_active_fg_color\", &prefs.ui_tab_active_fg_color, PREFS_COLOR, 0 },\n      { \"ui_tab_fg_color\", &prefs.ui_tab_fg_color, PREFS_COLOR, 0 },\n      { \"ui_text_bg_color\", &prefs.ui_text_bg_color, PREFS_COLOR, 0 },\n      { \"penalty_hyphen\", &prefs.penalty_hyphen, PREFS_FRACTION_100, 0 },\n      { \"penalty_hyphen_2\", &prefs.penalty_hyphen_2, PREFS_FRACTION_100, 0 },\n      { \"penalty_em_dash_left\", &prefs.penalty_em_dash_left,\n        PREFS_FRACTION_100, 0 },\n      { \"penalty_em_dash_right\", &prefs.penalty_em_dash_right,\n        PREFS_FRACTION_100, 0 },\n      { \"penalty_em_dash_right_2\", &prefs.penalty_em_dash_right_2,\n        PREFS_FRACTION_100, 0 },\n      { \"stretchability_factor\", &prefs.stretchability_factor,\n        PREFS_FRACTION_100, 0 }\n   };\n   // changing the LC_NUMERIC locale (temporarily) to C\n   // avoids parsing problems with float numbers\n   oldLocale = dStrdup(setlocale(LC_NUMERIC, NULL));\n   setlocale(LC_NUMERIC, \"C\");\n\n   // scan the file line by line\n   while ((line = dGetline(fp)) != NULL) {\n      st = dParser_parse_rc_line(&line, &name, &value);\n\n      if (st == 0) {\n         _MSG(\"prefsparser: name=%s, value=%s\\n\", name, value);\n         parseOption(name, value, symbols, sizeof(symbols) / sizeof(symbols[0]));\n      } else if (st < 0) {\n         MSG_ERR(\"prefsparser: Syntax error in dillorc line %d:\"\n                 \" name=\\\"%s\\\" value=\\\"%s\\\"\\n\", line_number, name, value);\n      }\n      dFree(line);\n      line_number++;\n   }\n   fclose(fp);\n\n   // restore the old numeric locale\n   setlocale(LC_NUMERIC, oldLocale);\n   dFree(oldLocale);\n}\n"
  },
  {
    "path": "src/prefsparser.hh",
    "content": "/*\n * Preferences parser\n *\n * Copyright (C) 2006-2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#ifndef __PREFS_HH__\n#define __PREFS_HH__\n\n#ifdef __cplusplus\nclass PrefsParser {\npublic:\n   static int parseLine(char *line, char *name, char *value);\n   static void parse(FILE *fp);\n};\n#endif /* __cplusplus */\n\n#endif /* __PREFS_HH__ */\n"
  },
  {
    "path": "src/srch",
    "content": "#!/bin/sh\n#\n# Find a token within source files ( *.[ch] )\n# Enjoy!\n# Jorge.-\n\nif [ $# = 1 ]; then\n  FLAGS=\"-H\"\nelif [ $# = 2 ]; then\n  FLAGS=\"-H $1\"\n  shift 1\nelse\n  echo \"Usage:\"\n  echo \"   srch [-options] <token>\"\n  exit 0\nfi\n\n#  find \"./\" -name \"*.[ch]\" -print -exec grep $1 {} \\;\negrep $FLAGS \"$1\" *.[ch][ch]\negrep $FLAGS \"$1\" *.[ch]\negrep $FLAGS \"$1\" IO/*.[ch][ch]*\negrep $FLAGS \"$1\" IO/*.[ch]\negrep $FLAGS \"$1\" ../dpi/*.[ch]\negrep $FLAGS \"$1\" ../dpi/*.[ch][ch]\negrep $FLAGS \"$1\" ../dpid/*.[ch]\negrep $FLAGS \"$1\" ../dpip/*.[ch]\negrep $FLAGS \"$1\" ../dlib/*.[ch]\n\n#egrep $FLAGS \"$1\" dw/*[ch]\n#egrep $FLAGS \"$1\" lout/*[ch]\negrep $FLAGS \"$1\" ../dw/*[ch]\negrep $FLAGS \"$1\" ../lout/*[ch]\n\n"
  },
  {
    "path": "src/styleengine.cc",
    "content": "/*\n * File: styleengine.cc\n *\n * Copyright 2008-2009 Johannes Hofmann <Johannes.Hofmann@gmx.de>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include \"../dlib/dlib.h\"\n#include \"msg.h\"\n#include \"prefs.h\"\n#include \"misc.h\"\n#include \"html_common.hh\"\n#include \"styleengine.hh\"\n#include \"web.hh\"\n#include \"capi.h\"\n\nusing namespace lout::misc;\nusing namespace dw::core::style;\n\n/**\n * Signal handler for \"delete\": This handles the case when an instance\n * of StyleImage is deleted, possibly when the cache client is still\n * active.\n *\n * \\todo Not neccessary for dw::Image? (dw::Image also implements\n * lout::signal::ObservedObject.)\n */\nclass StyleImageDeletionReceiver:\n   public lout::signal::ObservedObject::DeletionReceiver\n{\n   int clientKey;\n\npublic:\n   StyleImageDeletionReceiver (int clientKey);\n   ~StyleImageDeletionReceiver ();\n\n   void deleted (lout::signal::ObservedObject *object);\n};\n\nStyleImageDeletionReceiver::StyleImageDeletionReceiver (int clientKey)\n{\n   this->clientKey = clientKey;\n}\n\nStyleImageDeletionReceiver::~StyleImageDeletionReceiver ()\n{\n}\n\nvoid StyleImageDeletionReceiver::deleted (lout::signal::ObservedObject *object)\n{\n   a_Capi_stop_client (clientKey, 0);\n   delete this;\n}\n\n// ----------------------------------------------------------------------\n\nStyleEngine::StyleEngine (dw::core::Layout *layout,\n                          const DilloUrl *pageUrl, const DilloUrl *baseUrl) {\n   StyleAttrs style_attrs;\n   FontAttrs font_attrs;\n\n   doctree = new Doctree ();\n   stack = new lout::misc::SimpleVector <Node> (1);\n   cssContext = new CssContext ();\n   buildUserStyle ();\n   if(prefs.load_reader_mode_css) {\n      buildReaderModeStyle ();\n   }\n   this->layout = layout;\n   this->pageUrl = pageUrl ? a_Url_dup(pageUrl) : NULL;\n   this->baseUrl = baseUrl ? a_Url_dup(baseUrl) : NULL;\n   importDepth = 0;\n   dpmm = layout->dpiX () / 25.4; /* assume dpiX == dpiY */\n\n   stackPush ();\n   Node *n = stack->getLastRef ();\n\n   /* Create a dummy font, attribute, and tag for the bottom of the stack. */\n   font_attrs.name = prefs.font_sans_serif;\n   font_attrs.size = roundInt(14 * prefs.font_factor);\n   if (font_attrs.size < prefs.font_min_size)\n      font_attrs.size = prefs.font_min_size;\n   if (font_attrs.size > prefs.font_max_size)\n      font_attrs.size = prefs.font_max_size;\n   font_attrs.weight = 400;\n   font_attrs.style = FONT_STYLE_NORMAL;\n   font_attrs.letterSpacing = 0;\n   font_attrs.fontVariant = FONT_VARIANT_NORMAL;\n\n   style_attrs.initValues ();\n   style_attrs.font = Font::create (layout, &font_attrs);\n   style_attrs.color = Color::create (layout, 0);\n   style_attrs.backgroundColor = Color::create (layout, prefs.bg_color);\n\n   n->style = Style::create (&style_attrs);\n}\n\nStyleEngine::~StyleEngine () {\n   while (doctree->top ())\n      endElement (doctree->top ()->element);\n\n   stackPop (); // dummy node on the bottom of the stack\n   assert (stack->size () == 0);\n\n   a_Url_free(pageUrl);\n   a_Url_free(baseUrl);\n\n   delete stack;\n   delete doctree;\n   delete cssContext;\n}\n\nvoid StyleEngine::stackPush () {\n   static const Node emptyNode = {\n      NULL, NULL, NULL, NULL, NULL, NULL, false, false, NULL\n   };\n\n   stack->setSize (stack->size () + 1, emptyNode);\n}\n\nvoid StyleEngine::stackPop () {\n   Node *n = stack->getRef (stack->size () - 1);\n\n   delete n->styleAttrProperties;\n   delete n->styleAttrPropertiesImportant;\n   delete n->nonCssProperties;\n   if (n->style)\n      n->style->unref ();\n   if (n->wordStyle)\n      n->wordStyle->unref ();\n   if (n->backgroundStyle)\n      n->backgroundStyle->unref ();\n   stack->setSize (stack->size () - 1);\n}\n\n/**\n * \\brief tell the styleEngine that a new html element has started.\n */\nvoid StyleEngine::startElement (int element, BrowserWindow *bw) {\n   style (bw); // ensure that style of current node is computed\n\n   stackPush ();\n   Node *n = stack->getLastRef ();\n   DoctreeNode *dn = doctree->push ();\n\n   dn->element = element;\n   n->doctreeNode = dn;\n   if (stack->size () > 1)\n      n->displayNone = stack->getRef (stack->size () - 2)->displayNone;\n}\n\nvoid StyleEngine::startElement (const char *tagname, BrowserWindow *bw) {\n   startElement (a_Html_tag_index (tagname), bw);\n}\n\nvoid StyleEngine::setId (const char *id) {\n   DoctreeNode *dn = doctree->top ();\n   assert (dn->id == NULL);\n   dn->id = dStrdup (id);\n}\n\n/**\n * \\brief split a string at sep chars and return a SimpleVector of strings\n */\nstatic lout::misc::SimpleVector<char *> *splitStr (const char *str, char sep) {\n   const char *p1 = NULL;\n   lout::misc::SimpleVector<char *> *list =\n      new lout::misc::SimpleVector<char *> (1);\n\n   for (;; str++) {\n      if (*str != '\\0' && *str != sep) {\n         if (!p1)\n            p1 = str;\n      } else if (p1) {\n         list->increase ();\n         list->set (list->size () - 1, dStrndup (p1, str - p1));\n         p1 = NULL;\n      }\n\n      if (*str == '\\0')\n         break;\n   }\n\n   return list;\n}\n\nvoid StyleEngine::setClass (const char *klass) {\n   DoctreeNode *dn = doctree->top ();\n   assert (dn->klass == NULL);\n   dn->klass = splitStr (klass, ' ');\n}\n\nvoid StyleEngine::setStyle (const char *styleAttr) {\n   Node *n = stack->getRef (stack->size () - 1);\n   assert (n->styleAttrProperties == NULL);\n   // parse style information from style=\"\" attribute, if it exists\n   if (styleAttr && prefs.parse_embedded_css) {\n      n->styleAttrProperties = new CssPropertyList (true);\n      n->styleAttrPropertiesImportant = new CssPropertyList (true);\n\n      CssParser::parseDeclarationBlock (baseUrl, styleAttr, strlen (styleAttr),\n                                        n->styleAttrProperties,\n                                        n->styleAttrPropertiesImportant);\n   }\n}\n\n/**\n * \\brief Instruct StyleEngine to use the nonCssHints from parent element\n * This is only used for tables where nonCssHints on the TABLE-element\n * (e.g. border=1) also affect child elements like TD.\n */\nvoid StyleEngine::inheritNonCssHints () {\n   Node *pn = stack->getRef (stack->size () - 2);\n\n   if (pn->nonCssProperties) {\n      Node *n = stack->getRef (stack->size () - 1);\n      CssPropertyList *origNonCssProperties = n->nonCssProperties;\n\n      n->nonCssProperties = new CssPropertyList(*pn->nonCssProperties, true);\n\n      if (origNonCssProperties) // original nonCssProperties have precedence\n         origNonCssProperties->apply (n->nonCssProperties);\n\n      delete origNonCssProperties;\n   }\n}\n\nvoid StyleEngine::clearNonCssHints () {\n   Node *n = stack->getRef (stack->size () - 1);\n\n   delete n->nonCssProperties;\n   n->nonCssProperties = NULL;\n}\n\n/**\n * \\brief Use of the background color of the parent style as default.\n *   This is only used in table code to allow for colors specified for\n *   table rows as table rows are currently no widgets and therefore\n *   don't draw any background.\n */\nvoid StyleEngine::inheritBackgroundColor () {\n   stack->getRef (stack->size () - 1)->inheritBackgroundColor = true;\n}\n\ndw::core::style::Color *StyleEngine::backgroundColor () {\n   for (int i = 1; i < stack->size (); i++) {\n      Node *n = stack->getRef (i);\n\n      if (n->style && n->style->backgroundColor)\n         return n->style->backgroundColor;\n   }\n\n   return NULL;\n}\n\ndw::core::style::StyleImage *StyleEngine::backgroundImage\n   (dw::core::style::BackgroundRepeat *bgRepeat,\n    dw::core::style::BackgroundAttachment *bgAttachment,\n    dw::core::style::Length *bgPositionX,\n    dw::core::style::Length *bgPositionY) {\n   for (int i = 1; i < stack->size (); i++) {\n      Node *n = stack->getRef (i);\n\n      if (n->style && n->style->backgroundImage) {\n         *bgRepeat = n->style->backgroundRepeat;\n         *bgAttachment = n->style->backgroundAttachment;\n         *bgPositionX = n->style->backgroundPositionX;\n         *bgPositionY = n->style->backgroundPositionY;\n         return n->style->backgroundImage;\n      }\n   }\n\n   return NULL;\n}\n\n/**\n * \\brief set the CSS pseudo class :link.\n */\nvoid StyleEngine::setPseudoLink () {\n   DoctreeNode *dn = doctree->top ();\n   dn->pseudo = \"link\";\n}\n\n/**\n * \\brief set the CSS pseudo class :visited.\n */\nvoid StyleEngine::setPseudoVisited () {\n   DoctreeNode *dn = doctree->top ();\n   dn->pseudo = \"visited\";\n}\n\n/**\n * \\brief tell the styleEngine that a html element has ended.\n */\nvoid StyleEngine::endElement (int element) {\n   assert (element == doctree->top ()->element);\n\n   stackPop ();\n   doctree->pop ();\n}\n\nvoid StyleEngine::preprocessAttrs (dw::core::style::StyleAttrs *attrs) {\n   /* workaround for styling of inline elements */\n   if (stack->getRef (stack->size () - 2)->inheritBackgroundColor) {\n      attrs->backgroundColor =\n         stack->getRef (stack->size () - 2)->style->backgroundColor;\n      attrs->backgroundImage =\n         stack->getRef (stack->size () - 2)->style->backgroundImage;\n      attrs->backgroundRepeat =\n         stack->getRef (stack->size () - 2)->style->backgroundRepeat;\n      attrs->backgroundAttachment =\n         stack->getRef (stack->size () - 2)->style->backgroundAttachment;\n      attrs->backgroundPositionX =\n         stack->getRef (stack->size () - 2)->style->backgroundPositionX;\n      attrs->backgroundPositionY =\n         stack->getRef (stack->size () - 2)->style->backgroundPositionY;\n\n      attrs->valign = stack->getRef (stack->size () - 2)->style->valign;\n   }\n   attrs->borderColor.top = (Color *) -1;\n   attrs->borderColor.bottom = (Color *) -1;\n   attrs->borderColor.left = (Color *) -1;\n   attrs->borderColor.right = (Color *) -1;\n   /* initial value of border-width is 'medium' */\n   attrs->borderWidth.top = 2;\n   attrs->borderWidth.bottom = 2;\n   attrs->borderWidth.left = 2;\n   attrs->borderWidth.right = 2;\n}\n\nvoid StyleEngine::postprocessAttrs (dw::core::style::StyleAttrs *attrs) {\n   /* if border-color is not specified, use color as computed value */\n   if (attrs->borderColor.top == (Color *) -1)\n      attrs->borderColor.top = attrs->color;\n   if (attrs->borderColor.bottom == (Color *) -1)\n      attrs->borderColor.bottom = attrs->color;\n   if (attrs->borderColor.left == (Color *) -1)\n      attrs->borderColor.left = attrs->color;\n   if (attrs->borderColor.right == (Color *) -1)\n      attrs->borderColor.right = attrs->color;\n   /* computed value of border-width is 0 if border-style\n      is 'none' or 'hidden' */\n   if (attrs->borderStyle.top == BORDER_NONE ||\n       attrs->borderStyle.top == BORDER_HIDDEN)\n      attrs->borderWidth.top = 0;\n   if (attrs->borderStyle.bottom == BORDER_NONE ||\n       attrs->borderStyle.bottom == BORDER_HIDDEN)\n      attrs->borderWidth.bottom = 0;\n   if (attrs->borderStyle.left == BORDER_NONE ||\n       attrs->borderStyle.left == BORDER_HIDDEN)\n      attrs->borderWidth.left = 0;\n   if (attrs->borderStyle.right == BORDER_NONE ||\n       attrs->borderStyle.right == BORDER_HIDDEN)\n      attrs->borderWidth.right = 0;\n}\n\n/**\n * \\brief Make changes to StyleAttrs attrs according to CssPropertyList props.\n */\nvoid StyleEngine::apply (int i, StyleAttrs *attrs, CssPropertyList *props,\n                         BrowserWindow *bw) {\n   FontAttrs fontAttrs = *attrs->font;\n   Font *parentFont = stack->get (i - 1).style->font;\n   char *c, *fontName;\n   int lineHeight;\n   DilloUrl *imgUrl = NULL;\n\n   /* Determine font first so it can be used to resolve relative lengths. */\n   for (int j = 0; j < props->size (); j++) {\n      CssProperty *p = props->getRef (j);\n\n      switch (p->name) {\n         case CSS_PROPERTY_FONT_FAMILY:\n            // Check font names in comma separated list.\n            // Note, that p->value.strVal is modified, so that in future calls\n            // the matching font name can be used directly.\n            fontName = NULL;\n            while (p->value.strVal) {\n               if ((c = strchr(p->value.strVal, ',')))\n                  *c = '\\0';\n               dStrstrip(p->value.strVal);\n\n               if (dStrAsciiCasecmp (p->value.strVal, \"serif\") == 0)\n                  fontName = prefs.font_serif;\n               else if (dStrAsciiCasecmp (p->value.strVal, \"sans-serif\") == 0)\n                  fontName = prefs.font_sans_serif;\n               else if (dStrAsciiCasecmp (p->value.strVal, \"cursive\") == 0)\n                  fontName = prefs.font_cursive;\n               else if (dStrAsciiCasecmp (p->value.strVal, \"fantasy\") == 0)\n                  fontName = prefs.font_fantasy;\n               else if (dStrAsciiCasecmp (p->value.strVal, \"monospace\") == 0)\n                  fontName = prefs.font_monospace;\n               else if (Font::exists(layout, p->value.strVal))\n                  fontName = p->value.strVal;\n\n               if (fontName) {   // font found\n                  fontAttrs.name = fontName;\n                  break;\n               } else if (c) {   // try next from list\n                  memmove(p->value.strVal, c + 1, strlen(c + 1) + 1);\n               } else {          // no font found\n                  break;\n               }\n            }\n\n            break;\n         case CSS_PROPERTY_FONT_SIZE:\n            if (p->type == CSS_TYPE_ENUM) {\n               switch (p->value.intVal) {\n                  case CSS_FONT_SIZE_XX_SMALL:\n                     fontAttrs.size = roundInt(8.1 * prefs.font_factor);\n                     break;\n                  case CSS_FONT_SIZE_X_SMALL:\n                     fontAttrs.size = roundInt(9.7 * prefs.font_factor);\n                     break;\n                  case CSS_FONT_SIZE_SMALL:\n                     fontAttrs.size = roundInt(11.7 * prefs.font_factor);\n                     break;\n                  case CSS_FONT_SIZE_MEDIUM:\n                     fontAttrs.size = roundInt(14.0 * prefs.font_factor);\n                     break;\n                  case CSS_FONT_SIZE_LARGE:\n                     fontAttrs.size = roundInt(16.8 * prefs.font_factor);\n                     break;\n                  case CSS_FONT_SIZE_X_LARGE:\n                     fontAttrs.size = roundInt(20.2 * prefs.font_factor);\n                     break;\n                  case CSS_FONT_SIZE_XX_LARGE:\n                     fontAttrs.size = roundInt(24.2 * prefs.font_factor);\n                     break;\n                  case CSS_FONT_SIZE_SMALLER:\n                     fontAttrs.size = roundInt(fontAttrs.size * 0.83);\n                     break;\n                  case CSS_FONT_SIZE_LARGER:\n                     fontAttrs.size = roundInt(fontAttrs.size * 1.2);\n                     break;\n                  default:\n                     assert(false); // invalid font-size enum\n               }\n            } else {\n               computeValue (&fontAttrs.size, p->value.intVal, parentFont,\n                  parentFont->size);\n            }\n\n            if (fontAttrs.size < prefs.font_min_size)\n               fontAttrs.size = prefs.font_min_size;\n            if (fontAttrs.size > prefs.font_max_size)\n               fontAttrs.size = prefs.font_max_size;\n\n            break;\n         case CSS_PROPERTY_FONT_STYLE:\n            fontAttrs.style = (FontStyle) p->value.intVal;\n            break;\n         case CSS_PROPERTY_FONT_WEIGHT:\n\n            if (p->type == CSS_TYPE_ENUM) {\n               switch (p->value.intVal) {\n                  case CSS_FONT_WEIGHT_BOLD:\n                     fontAttrs.weight = 700;\n                     break;\n                  case CSS_FONT_WEIGHT_BOLDER:\n                     fontAttrs.weight += 300;\n                     break;\n                  case CSS_FONT_WEIGHT_LIGHT:\n                     fontAttrs.weight = 100;\n                     break;\n                  case CSS_FONT_WEIGHT_LIGHTER:\n                     fontAttrs.weight -= 300;\n                     break;\n                  case CSS_FONT_WEIGHT_NORMAL:\n                     fontAttrs.weight = 400;\n                     break;\n                  default:\n                     assert(false); // invalid font weight value\n                     break;\n               }\n            } else {\n               fontAttrs.weight = p->value.intVal;\n            }\n\n            if (fontAttrs.weight < 100)\n               fontAttrs.weight = 100;\n            if (fontAttrs.weight > 900)\n               fontAttrs.weight = 900;\n\n            break;\n         case CSS_PROPERTY_LETTER_SPACING:\n            if (p->type == CSS_TYPE_ENUM) {\n               if (p->value.intVal == CSS_LETTER_SPACING_NORMAL) {\n                  fontAttrs.letterSpacing = 0;\n               }\n            } else {\n               computeValue (&fontAttrs.letterSpacing, p->value.intVal,\n                  parentFont, parentFont->size);\n            }\n\n            /* Limit letterSpacing to reasonable values to avoid overflows e.g,\n             * when measuring word width.\n             */\n            if (fontAttrs.letterSpacing > 1000)\n               fontAttrs.letterSpacing = 1000;\n            else if (fontAttrs.letterSpacing < -1000)\n               fontAttrs.letterSpacing = -1000;\n            break;\n         case CSS_PROPERTY_FONT_VARIANT:\n            fontAttrs.fontVariant = (FontVariant) p->value.intVal;\n            break;\n         default:\n            break;\n      }\n   }\n\n   attrs->font = Font::create (layout, &fontAttrs);\n\n   for (int j = 0; j < props->size (); j++) {\n      CssProperty *p = props->getRef (j);\n\n      switch (p->name) {\n         /* \\todo missing cases */\n         case CSS_PROPERTY_BACKGROUND_ATTACHMENT:\n            attrs->backgroundAttachment =\n               (BackgroundAttachment) p->value.intVal;\n            break;\n         case CSS_PROPERTY_BACKGROUND_COLOR:\n            if (prefs.allow_white_bg || p->value.intVal != 0xffffff)\n               attrs->backgroundColor = Color::create(layout, p->value.intVal);\n            else\n               attrs->backgroundColor =\n                  Color::create(layout, prefs.white_bg_replacement);\n            break;\n         case CSS_PROPERTY_BACKGROUND_IMAGE:\n            // p->value.strVal should be absolute, so baseUrl is not needed\n            imgUrl = a_Url_new (p->value.strVal, NULL);\n            break;\n         case CSS_PROPERTY_BACKGROUND_POSITION:\n            computeLength (&attrs->backgroundPositionX, p->value.posVal->posX,\n                           attrs->font);\n            computeLength (&attrs->backgroundPositionY, p->value.posVal->posY,\n                           attrs->font);\n            break;\n         case CSS_PROPERTY_BACKGROUND_REPEAT:\n            attrs->backgroundRepeat = (BackgroundRepeat) p->value.intVal;\n            break;\n         case CSS_PROPERTY_BORDER_COLLAPSE:\n            attrs->borderCollapse = (BorderCollapse) p->value.intVal;\n            break;\n         case CSS_PROPERTY_BORDER_TOP_COLOR:\n            attrs->borderColor.top = (p->type == CSS_TYPE_ENUM) ? NULL :\n                                     Color::create (layout, p->value.intVal);\n            break;\n         case CSS_PROPERTY_BORDER_BOTTOM_COLOR:\n            attrs->borderColor.bottom = (p->type == CSS_TYPE_ENUM) ? NULL :\n                                       Color::create (layout, p->value.intVal);\n            break;\n         case CSS_PROPERTY_BORDER_LEFT_COLOR:\n            attrs->borderColor.left = (p->type == CSS_TYPE_ENUM) ? NULL :\n                                      Color::create (layout, p->value.intVal);\n            break;\n         case CSS_PROPERTY_BORDER_RIGHT_COLOR:\n            attrs->borderColor.right = (p->type == CSS_TYPE_ENUM) ? NULL :\n                                       Color::create (layout, p->value.intVal);\n            break;\n         case CSS_PROPERTY_BORDER_BOTTOM_STYLE:\n            attrs->borderStyle.bottom = (BorderStyle) p->value.intVal;\n            break;\n         case CSS_PROPERTY_BORDER_LEFT_STYLE:\n            attrs->borderStyle.left = (BorderStyle) p->value.intVal;\n            break;\n         case CSS_PROPERTY_BORDER_RIGHT_STYLE:\n            attrs->borderStyle.right = (BorderStyle) p->value.intVal;\n            break;\n         case CSS_PROPERTY_BORDER_TOP_STYLE:\n            attrs->borderStyle.top = (BorderStyle) p->value.intVal;\n            break;\n         case CSS_PROPERTY_BORDER_BOTTOM_WIDTH:\n            computeBorderWidth (&attrs->borderWidth.bottom, p, attrs->font);\n            break;\n         case CSS_PROPERTY_BORDER_LEFT_WIDTH:\n            computeBorderWidth (&attrs->borderWidth.left, p, attrs->font);\n            break;\n         case CSS_PROPERTY_BORDER_RIGHT_WIDTH:\n            computeBorderWidth (&attrs->borderWidth.right, p, attrs->font);\n            break;\n         case CSS_PROPERTY_BORDER_TOP_WIDTH:\n            computeBorderWidth (&attrs->borderWidth.top, p, attrs->font);\n            break;\n         case CSS_PROPERTY_BORDER_SPACING:\n            computeValue (&attrs->hBorderSpacing, p->value.intVal,attrs->font);\n            computeValue (&attrs->vBorderSpacing, p->value.intVal,attrs->font);\n            break;\n         case CSS_PROPERTY_BOTTOM:\n            computeLength (&attrs->bottom, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_CLEAR:\n            attrs->clear = (ClearType) p->value.intVal;\n            break;\n         case CSS_PROPERTY_COLOR:\n            attrs->color = Color::create (layout, p->value.intVal);\n            break;\n         case CSS_PROPERTY_CURSOR:\n            attrs->cursor = (Cursor) p->value.intVal;\n            break;\n         case CSS_PROPERTY_DISPLAY:\n            attrs->display = (DisplayType) p->value.intVal;\n            if (attrs->display == DISPLAY_NONE)\n               stack->getRef (i)->displayNone = true;\n            break;\n         case CSS_PROPERTY_FLOAT:\n            attrs->vloat = (FloatType) p->value.intVal;\n            break;\n         case CSS_PROPERTY_LEFT:\n            computeLength (&attrs->left, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_LINE_HEIGHT:\n            if (p->type == CSS_TYPE_ENUM) { //only valid enum value is \"normal\"\n               attrs->lineHeight = dw::core::style::LENGTH_AUTO;\n            } else if (p->type == CSS_TYPE_LENGTH_PERCENTAGE_NUMBER) {\n               if (CSS_LENGTH_TYPE (p->value.intVal) == CSS_LENGTH_TYPE_NONE) {\n                  attrs->lineHeight =\n                     createPerLength(CSS_LENGTH_VALUE(p->value.intVal));\n               } else if (computeValue (&lineHeight, p->value.intVal,\n                                        attrs->font, attrs->font->size)) {\n                  attrs->lineHeight = createAbsLength(lineHeight);\n               }\n            }\n            break;\n         case CSS_PROPERTY_LIST_STYLE_POSITION:\n            attrs->listStylePosition = (ListStylePosition) p->value.intVal;\n            break;\n         case CSS_PROPERTY_LIST_STYLE_TYPE:\n            attrs->listStyleType = (ListStyleType) p->value.intVal;\n            break;\n         case CSS_PROPERTY_MARGIN_BOTTOM:\n            computeValue (&attrs->margin.bottom, p->value.intVal, attrs->font);\n            if (attrs->margin.bottom < 0) // \\todo fix negative margins in dw/*\n               attrs->margin.bottom = 0;\n            break;\n         case CSS_PROPERTY_MARGIN_LEFT:\n            computeValue (&attrs->margin.left, p->value.intVal, attrs->font);\n            if (attrs->margin.left < 0) // \\todo fix negative margins in dw/*\n               attrs->margin.left = 0;\n            break;\n         case CSS_PROPERTY_MARGIN_RIGHT:\n            computeValue (&attrs->margin.right, p->value.intVal, attrs->font);\n            if (attrs->margin.right < 0) // \\todo fix negative margins in dw/*\n               attrs->margin.right = 0;\n            break;\n         case CSS_PROPERTY_MARGIN_TOP:\n            computeValue (&attrs->margin.top, p->value.intVal, attrs->font);\n            if (attrs->margin.top < 0) // \\todo fix negative margins in dw/*\n               attrs->margin.top = 0;\n            break;\n         case CSS_PROPERTY_OVERFLOW:\n            attrs->overflow = (Overflow) p->value.intVal;\n            break;\n         case CSS_PROPERTY_PADDING_TOP:\n            computeValue (&attrs->padding.top, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_PADDING_BOTTOM:\n            computeValue (&attrs->padding.bottom, p->value.intVal,attrs->font);\n            break;\n         case CSS_PROPERTY_PADDING_LEFT:\n            computeValue (&attrs->padding.left, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_PADDING_RIGHT:\n            computeValue (&attrs->padding.right, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_POSITION:\n            attrs->position = (Position) p->value.intVal;\n            break;\n         case CSS_PROPERTY_RIGHT:\n            computeLength (&attrs->right, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_TEXT_ALIGN:\n            attrs->textAlign = (TextAlignType) p->value.intVal;\n            break;\n         case CSS_PROPERTY_TEXT_DECORATION:\n            attrs->textDecoration |= p->value.intVal;\n            break;\n         case CSS_PROPERTY_TEXT_INDENT:\n            computeLength (&attrs->textIndent, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_TEXT_TRANSFORM:\n            attrs->textTransform = (TextTransform) p->value.intVal;\n            break;\n         case CSS_PROPERTY_TOP:\n            computeLength (&attrs->top, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_VERTICAL_ALIGN:\n            attrs->valign = (VAlignType) p->value.intVal;\n            break;\n         case CSS_PROPERTY_WHITE_SPACE:\n            attrs->whiteSpace = (WhiteSpace) p->value.intVal;\n            break;\n         case CSS_PROPERTY_WIDTH:\n            computeLength (&attrs->width, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_HEIGHT:\n            computeLength (&attrs->height, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_WORD_SPACING:\n            if (p->type == CSS_TYPE_ENUM) {\n               if (p->value.intVal == CSS_WORD_SPACING_NORMAL) {\n                  attrs->wordSpacing = 0;\n               }\n            } else {\n               computeValue(&attrs->wordSpacing, p->value.intVal, attrs->font);\n            }\n\n            /* Limit to reasonable values to avoid overflows */\n            if (attrs->wordSpacing > 1000)\n               attrs->wordSpacing = 1000;\n            else if (attrs->wordSpacing < -1000)\n               attrs->wordSpacing = -1000;\n            break;\n         case CSS_PROPERTY_MIN_WIDTH:\n            computeLength (&attrs->minWidth, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_MAX_WIDTH:\n            computeLength (&attrs->maxWidth, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_MIN_HEIGHT:\n            computeLength (&attrs->minHeight, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_MAX_HEIGHT:\n            computeLength (&attrs->maxHeight, p->value.intVal, attrs->font);\n            break;\n         case CSS_PROPERTY_Z_INDEX:\n            if (p->type == CSS_LENGTH_TYPE_AUTO)\n               attrs->zIndex = dw::core::style::Z_INDEX_AUTO;\n            else\n               attrs->zIndex = p->value.intVal;\n            break;\n         case PROPERTY_X_LINK:\n            attrs->x_link = p->value.intVal;\n            break;\n         case PROPERTY_X_LANG:\n            attrs->x_lang[0] = D_ASCII_TOLOWER(p->value.strVal[0]);\n            if (attrs->x_lang[0])\n               attrs->x_lang[1] = D_ASCII_TOLOWER(p->value.strVal[1]);\n            else\n               attrs->x_lang[1] = 0;\n            break;\n         case PROPERTY_X_IMG:\n            attrs->x_img = p->value.intVal;\n            break;\n         case PROPERTY_X_TOOLTIP:\n            attrs->x_tooltip = Tooltip::create(layout, p->value.strVal);\n            break;\n         default:\n            break;\n      }\n   }\n\n   if (imgUrl && prefs.load_background_images &&\n       !stack->getRef (i)->displayNone &&\n       !(URL_FLAGS(pageUrl) & URL_SpamSafe))\n   {\n      attrs->backgroundImage = StyleImage::create();\n      DilloImage *image =\n         a_Image_new(layout,\n            (void*)attrs->backgroundImage\n            ->getMainImgRenderer(),\n            0xffffff);\n\n      // we use the pageUrl as requester to prevent cross\n      // domain requests as specified in domainrc\n      DilloWeb *web = a_Web_new(bw, imgUrl, pageUrl);\n      web->Image = image;\n      a_Image_ref(image);\n      web->flags |= WEB_Image;\n\n      int clientKey;\n      if ((clientKey = a_Capi_open_url(web, NULL, NULL)) != 0) {\n                  a_Bw_add_client(bw, clientKey, 0);\n                  a_Bw_add_url(bw, imgUrl);\n                  attrs->backgroundImage->connectDeletion\n                     (new StyleImageDeletionReceiver (clientKey));\n      }\n   }\n   a_Url_free (imgUrl);\n}\n\n/**\n * \\brief Resolve relative lengths to absolute values.\n */\nbool StyleEngine::computeValue (int *dest, CssLength value, Font *font) {\n   switch (CSS_LENGTH_TYPE (value)) {\n      case CSS_LENGTH_TYPE_PX:\n         *dest = (int) CSS_LENGTH_VALUE (value);\n         return true;\n      case CSS_LENGTH_TYPE_MM:\n         *dest = roundInt (CSS_LENGTH_VALUE (value) * dpmm);\n         return true;\n/*    case CSS_LENGTH_TYPE_CH: */\n      case CSS_LENGTH_TYPE_EM:\n         *dest = roundInt (CSS_LENGTH_VALUE (value) * font->size);\n         return true;\n      case CSS_LENGTH_TYPE_EX:\n         *dest = roundInt (CSS_LENGTH_VALUE(value) * font->xHeight);\n         return true;\n      case CSS_LENGTH_TYPE_NONE:\n         // length values other than 0 without unit are only allowed\n         // in special cases (line-height) and have to be handled\n         // separately.\n         assert ((int) CSS_LENGTH_VALUE (value) == 0);\n         *dest = 0;\n         return true;\n      default:\n         break;\n   }\n\n   return false;\n}\n\nbool StyleEngine::computeValue (int *dest, CssLength value, Font *font,\n                                int percentageBase) {\n   if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_PERCENTAGE) {\n      *dest = roundInt (CSS_LENGTH_VALUE (value) * percentageBase);\n      return true;\n   } else\n      return computeValue (dest, value, font);\n}\n\nbool StyleEngine::computeLength (dw::core::style::Length *dest,\n                                 CssLength value, Font *font) {\n   int v;\n\n   if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_PERCENTAGE) {\n      *dest = createPerLength (CSS_LENGTH_VALUE (value));\n      return true;\n   } else if (CSS_LENGTH_TYPE (value) == CSS_LENGTH_TYPE_AUTO) {\n      *dest = dw::core::style::LENGTH_AUTO;\n      return true;\n   } else if (computeValue (&v, value, font)) {\n      *dest = createAbsLength (v);\n      return true;\n   }\n\n   return false;\n}\n\nvoid StyleEngine::computeBorderWidth (int *dest, CssProperty *p,\n                                      dw::core::style::Font *font) {\n   if (p->type == CSS_TYPE_ENUM) {\n      switch (p->value.intVal) {\n         case CSS_BORDER_WIDTH_THIN:\n            *dest = 1;\n            break;\n         case CSS_BORDER_WIDTH_MEDIUM:\n            *dest = 2;\n            break;\n         case CSS_BORDER_WIDTH_THICK:\n            *dest = 3;\n            break;\n         default:\n            assert(false);\n      }\n   } else {\n      computeValue (dest, p->value.intVal, font);\n   }\n}\n\n/**\n * \\brief Similar to StyleEngine::style(), but with backgroundColor set.\n * A normal style might have backgroundColor == NULL to indicate a transparent\n * background. This method ensures that backgroundColor is set.\n */\nStyle * StyleEngine::backgroundStyle (BrowserWindow *bw) {\n   if (!stack->getRef (stack->size () - 1)->backgroundStyle) {\n      StyleAttrs attrs = *style (bw);\n\n      for (int i = stack->size () - 1; i >= 0 && ! attrs.backgroundColor; i--)\n         attrs.backgroundColor = stack->getRef (i)->style->backgroundColor;\n\n      assert (attrs.backgroundColor);\n      stack->getRef (stack->size () - 1)->backgroundStyle =\n         Style::create (&attrs);\n   }\n   return stack->getRef (stack->size () - 1)->backgroundStyle;\n}\n\n/**\n * \\brief Create a new style object based on the previously opened / closed\n * HTML elements and the nonCssProperties that have been set.\n * This method is private. Call style() to get a current style object.\n */\nStyle * StyleEngine::style0 (int i, BrowserWindow *bw) {\n   CssPropertyList props, *styleAttrProperties, *styleAttrPropertiesImportant;\n   CssPropertyList *nonCssProperties;\n   // get previous style from the stack\n   StyleAttrs attrs = *stack->getRef (i - 1)->style;\n\n   // Ensure that StyleEngine::style0() has not been called before for\n   // this element.\n   // Style computation is expensive so limit it as much as possible.\n   // If this assertion is hit, you need to rearrange the code that is\n   // doing styleEngine calls to call setNonCssHint() before calling\n   // style() or wordStyle() for each new element.\n   assert (stack->getRef (i)->style == NULL);\n\n   // reset values that are not inherited according to CSS\n   attrs.resetValues ();\n   preprocessAttrs (&attrs);\n\n   styleAttrProperties = stack->getRef (i)->styleAttrProperties;\n   styleAttrPropertiesImportant = stack->getRef(i)->styleAttrPropertiesImportant;\n   nonCssProperties = stack->getRef (i)->nonCssProperties;\n\n   // merge style information\n   cssContext->apply (&props, doctree, stack->getRef(i)->doctreeNode,\n                      styleAttrProperties, styleAttrPropertiesImportant,\n                      nonCssProperties);\n\n   // apply style\n   apply (i, &attrs, &props, bw);\n\n   postprocessAttrs (&attrs);\n\n   stack->getRef (i)->style = Style::create (&attrs);\n\n   return stack->getRef (i)->style;\n}\n\nStyle * StyleEngine::wordStyle0 (BrowserWindow *bw) {\n   StyleAttrs attrs = *style (bw);\n   attrs.resetValues ();\n\n   if (stack->getRef (stack->size() - 1)->inheritBackgroundColor) {\n      attrs.backgroundColor = style (bw)->backgroundColor;\n      attrs.backgroundImage = style (bw)->backgroundImage;\n      attrs.backgroundRepeat = style (bw)->backgroundRepeat;\n      attrs.backgroundAttachment = style (bw)->backgroundAttachment;\n      attrs.backgroundPositionX = style (bw)->backgroundPositionX;\n      attrs.backgroundPositionY = style (bw)->backgroundPositionY;\n   }\n\n   attrs.valign = style (bw)->valign;\n\n   stack->getRef(stack->size() - 1)->wordStyle = Style::create(&attrs);\n   return stack->getRef (stack->size () - 1)->wordStyle;\n}\n\n/**\n * \\brief Recompute all style information from scratch\n * This is used to take into account CSS styles for the HTML-element.\n * The CSS data is only completely available after parsing the HEAD-section\n * and thereby after the HTML-element has been opened.\n * Note that restyle() does not change any styles in the widget tree.\n */\nvoid StyleEngine::restyle (BrowserWindow *bw) {\n   for (int i = 1; i < stack->size (); i++) {\n      Node *n = stack->getRef (i);\n      if (n->style) {\n         n->style->unref ();\n         n->style = NULL;\n      }\n      if (n->wordStyle) {\n         n->wordStyle->unref ();\n         n->wordStyle = NULL;\n      }\n      if (n->backgroundStyle) {\n         n->backgroundStyle->unref ();\n         n->backgroundStyle = NULL;\n      }\n\n      style0 (i, bw);\n   }\n}\n\nvoid StyleEngine::parse (DilloHtml *html, DilloUrl *url, const char *buf,\n                         int buflen, CssOrigin origin) {\n   if (importDepth > 10) { // avoid looping with recursive @import directives\n      MSG_WARN(\"Maximum depth of CSS @import reached--ignoring stylesheet.\\n\");\n      return;\n   }\n\n   importDepth++;\n   CssParser::parse (html, url, cssContext, buf, buflen, origin);\n   importDepth--;\n}\n\n/**\n * \\brief Create the user agent style.\n *\n * The user agent style defines how dillo renders HTML in the absence of\n * author or user styles.\n */\nvoid StyleEngine::init () {\n   const char *cssBuf =\n      \"body  {margin: 5px}\"\n      \"big {font-size: 1.17em}\"\n      \"blockquote, dd {margin-left: 40px; margin-right: 40px}\"\n      \"center {text-align: center}\"\n      \"dt {font-weight: bolder}\"\n      \":link {color: blue; text-decoration: underline; cursor: pointer}\"\n      \":visited {color: #800080; text-decoration: underline; cursor: pointer}\"\n      \"h1, h2, h3, h4, h5, h6, b, strong {font-weight: bolder}\"\n      \"address, article, aside, center, div, figure, figcaption, footer,\"\n      \" h1, h2, h3, h4, h5, h6, header, nav, ol, p, pre, section, ul\"\n      \" {display: block}\"\n      \"i, em, cite, address, var {font-style: italic}\"\n      \":link img, :visited img {border: 1px solid}\"\n      \"frameset, ul, ol, dir {margin-left: 40px}\"\n      /* WORKAROUND: It should be margin: 1em 0\n       * but as we don't collapse these margins yet, it\n       * look better like this.\n       */\n      \"p {margin: 0.5em 0}\"\n      \"figure {margin: 1em 40px}\"\n      \"h1 {font-size: 2em; margin-top: .67em; margin-bottom: 0}\"\n      \"h2 {font-size: 1.5em; margin-top: .75em; margin-bottom: 0}\"\n      \"h3 {font-size: 1.17em; margin-top: .83em; margin-bottom: 0}\"\n      \"h4 {margin-top: 1.12em; margin-bottom: 0}\"\n      \"h5 {font-size: 0.83em; margin-top: 1.5em; margin-bottom: 0}\"\n      \"h6 {font-size: 0.75em; margin-top: 1.67em; margin-bottom: 0}\"\n      \"hr {width: 100%; border: 1px inset}\"\n      \"li {margin-top: 0.1em; display: list-item}\"\n      \"pre {white-space: pre}\"\n      \"ol {list-style-type: decimal}\"\n      \"ul {list-style-type: disc}\"\n      \"ul ul {list-style-type: circle}\"\n      \"ul ul ul {list-style-type: square}\"\n      \"ul ul ul ul {list-style-type: disc}\"\n      \"ins, u {text-decoration: underline}\"\n      \"small, sub, sup {font-size: 0.83em}\"\n      \"sub {vertical-align: sub}\"\n      \"sup {vertical-align: super}\"\n      \"s, strike, del {text-decoration: line-through}\"\n      /* HTML5 spec notes that mark styling \"is just a suggestion and can be\n       * changed based on implementation feedback\"\n       */\n      \"mark {background: yellow; color: black;}\"\n      \"table {border-spacing: 2px}\"\n      \"td, th {padding: 2px}\"\n      \"thead, tbody, tfoot {vertical-align: middle}\"\n      \"th {font-weight: bolder; text-align: center}\"\n      \"code, tt, pre, samp, kbd {font-family: monospace}\"\n      /* WORKAROUND: Reset font properties in tables as some\n       * pages rely on it (e.g. gmail).\n       * http://developer.mozilla.org/en-US/Fixing_Table_Inheritance_in_Quirks_Mode\n       * has a detailed description of the issue.\n       */\n      \"table, caption {font-size: medium; font-weight: normal}\";\n\n   CssContext context;\n   CssParser::parse (NULL, NULL, &context, cssBuf, strlen (cssBuf),\n                     CSS_ORIGIN_USER_AGENT);\n}\n\nvoid StyleEngine::buildUserStyle () {\n   Dstr *style;\n   char *filename = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/style.css\", NULL);\n   const char *sys_filename = DILLO_SYSCONF \"style.css\";\n\n   if ((style = a_Misc_file2dstr(filename))) {\n      CssParser::parse (NULL,NULL,cssContext,style->str, style->len,CSS_ORIGIN_USER);\n      dStr_free (style, 1);\n   } else if ((style = a_Misc_file2dstr(sys_filename))) {\n      CssParser::parse (NULL,NULL,cssContext,style->str, style->len,CSS_ORIGIN_USER);\n      dStr_free (style, 1);\n   }\n   dFree (filename);\n}\n\nvoid StyleEngine::buildReaderModeStyle () {\n   Dstr *style;\n   const char *sys_reader_mode_filename = DILLO_SYSCONF \"style_reader_mode.css\";\n\n   if ((style = a_Misc_file2dstr(sys_reader_mode_filename))) {\n      CssParser::parse (NULL,NULL,cssContext,style->str, style->len,CSS_ORIGIN_USER);\n      dStr_free (style, 1);\n   }\n}\n"
  },
  {
    "path": "src/styleengine.hh",
    "content": "#ifndef __STYLEENGINE_HH__\n#define __STYLEENGINE_HH__\n\nclass StyleEngine;\n\n#include \"dw/core.hh\"\n#include \"doctree.hh\"\n#include \"css.hh\"\n#include \"cssparser.hh\"\n\n/**\n * \\brief This class provides the glue between HTML parser and CSS subsystem.\n *\n * It maintains a document tree and creates and caches style objects for use\n * by the HTML parser.\n * The HTML parser in turn informs StyleEngine about opened or closed\n * HTML elements and their attributes via the startElement() / endElement()\n * methods.\n */\nclass StyleEngine {\n   private:\n      struct Node {\n         CssPropertyList *styleAttrProperties;\n         CssPropertyList *styleAttrPropertiesImportant;\n         CssPropertyList *nonCssProperties;\n         dw::core::style::Style *style;\n         dw::core::style::Style *wordStyle;\n         dw::core::style::Style *backgroundStyle;\n         bool inheritBackgroundColor;\n         bool displayNone;\n         DoctreeNode *doctreeNode;\n      };\n\n      dw::core::Layout *layout;\n      lout::misc::SimpleVector <Node> *stack;\n      CssContext *cssContext;\n      Doctree *doctree;\n      int importDepth;\n      float dpmm;\n      DilloUrl *pageUrl, *baseUrl;\n\n      void stackPush ();\n      void stackPop ();\n      void buildUserStyle ();\n      void buildReaderModeStyle ();\n      dw::core::style::Style *style0 (int i, BrowserWindow *bw);\n      dw::core::style::Style *wordStyle0 (BrowserWindow *bw);\n      inline void setNonCssHint(CssPropertyName name, CssValueType type,\n                                CssPropertyValue value) {\n         Node *n = stack->getRef (stack->size () - 1);\n\n         if (!n->nonCssProperties)\n            n->nonCssProperties = new CssPropertyList (true);\n         n->nonCssProperties->set(name, type, value);\n      }\n      void preprocessAttrs (dw::core::style::StyleAttrs *attrs);\n      void postprocessAttrs (dw::core::style::StyleAttrs *attrs);\n      void apply (int i, dw::core::style::StyleAttrs *attrs,\n                  CssPropertyList *props, BrowserWindow *bw);\n      bool computeValue (int *dest, CssLength value,\n                         dw::core::style::Font *font);\n      bool computeValue (int *dest, CssLength value,\n                         dw::core::style::Font *font, int percentageBase);\n      bool computeLength (dw::core::style::Length *dest, CssLength value,\n                          dw::core::style::Font *font);\n      void computeBorderWidth (int *dest, CssProperty *p,\n                               dw::core::style::Font *font);\n\n   public:\n      static void init ();\n\n      StyleEngine (dw::core::Layout *layout,\n                   const DilloUrl *pageUrl, const DilloUrl *baseUrl);\n      ~StyleEngine ();\n\n      void parse (DilloHtml *html, DilloUrl *url, const char *buf, int buflen,\n                  CssOrigin origin);\n      void startElement (int tag, BrowserWindow *bw);\n      void startElement (const char *tagname, BrowserWindow *bw);\n      void setId (const char *id);\n      const char * getId () { return doctree->top ()->id; };\n      void setClass (const char *klass);\n      void setStyle (const char *style);\n      void endElement (int tag);\n      void setPseudoLink ();\n      void setPseudoVisited ();\n      inline void setNonCssHint(CssPropertyName name, CssValueType type,\n                                int value) {\n         CssPropertyValue v;\n         v.intVal = value;\n         setNonCssHint (name, type, v);\n      }\n      inline void setNonCssHint(CssPropertyName name, CssValueType type,\n                                const char *value) {\n         CssPropertyValue v;\n         v.strVal = dStrdup(value);\n         setNonCssHint (name, type, v);\n      }\n      void inheritNonCssHints ();\n      void clearNonCssHints ();\n      void restyle (BrowserWindow *bw);\n      void inheritBackgroundColor (); /* \\todo get rid of this somehow */\n      dw::core::style::Style *backgroundStyle (BrowserWindow *bw);\n      dw::core::style::Color *backgroundColor ();\n      dw::core::style::StyleImage *backgroundImage\n         (dw::core::style::BackgroundRepeat *bgRepeat,\n          dw::core::style::BackgroundAttachment *bgAttachment,\n          dw::core::style::Length *bgPositionX,\n          dw::core::style::Length *bgPositionY);\n\n      inline dw::core::style::Style *style (BrowserWindow *bw) {\n         dw::core::style::Style *s = stack->getRef (stack->size () - 1)->style;\n         if (s)\n            return s;\n         else\n            return style0 (stack->size () - 1, bw);\n      };\n\n      inline dw::core::style::Style *wordStyle (BrowserWindow *bw) {\n         dw::core::style::Style *s = stack->getRef(stack->size()-1)->wordStyle;\n         if (s)\n            return s;\n         else\n            return wordStyle0 (bw);\n      };\n};\n\n#endif\n"
  },
  {
    "path": "src/table.cc",
    "content": "/*\n * File: table.cc\n *\n * Copyright 2008 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include \"table.hh\"\n#include \"html_common.hh\"\n\n#include \"dw/style.hh\"\n#include \"dw/textblock.hh\"\n#include \"dw/table.hh\"\n#include \"dw/simpletablecell.hh\"\n\n#include \"prefs.h\"\n#include \"msg.h\"\n#include \"css.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\n\n/*\n * Forward declarations\n */\n\nstatic void Html_tag_open_table_cell(DilloHtml *html,\n                                     const char *tag, int tagsize,\n                                     dw::core::style::TextAlignType text_align);\nstatic void Html_tag_content_table_cell(DilloHtml *html,\n                                        const char *tag, int tagsize);\n\n/*\n * <TABLE>\n */\nvoid Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   int32_t border = -1, cellspacing = -1, cellpadding = -1, bgcolor = -1;\n   CssLength cssLength;\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"border\")))\n      border = isdigit(attrbuf[0]) ? strtol (attrbuf, NULL, 10) : 1;\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"cellspacing\"))) {\n      cellspacing = strtol (attrbuf, NULL, 10);\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<table> cellspacing attribute is obsolete.\");\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"cellpadding\"))) {\n      cellpadding = strtol (attrbuf, NULL, 10);\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<table> cellpadding attribute is obsolete.\");\n   }\n\n   if (border != -1) {\n      cssLength = CSS_CREATE_LENGTH (border, CSS_LENGTH_TYPE_PX);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_OUTSET);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_OUTSET);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_OUTSET);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_OUTSET);\n   }\n\n   if (cellspacing != -1) {\n      cssLength = CSS_CREATE_LENGTH (cellspacing, CSS_LENGTH_TYPE_PX);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_SPACING,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"width\"))) {\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE,\n                                        a_Html_parse_length (html, attrbuf));\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<table> width attribute is obsolete.\");\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"align\"))) {\n      if (dStrAsciiCasecmp (attrbuf, \"left\") == 0)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN,\n                                           CSS_TYPE_ENUM, TEXT_ALIGN_LEFT);\n      else if (dStrAsciiCasecmp (attrbuf, \"right\") == 0)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN,\n                                           CSS_TYPE_ENUM, TEXT_ALIGN_RIGHT);\n      else if (dStrAsciiCasecmp (attrbuf, \"center\") == 0)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN,\n                                           CSS_TYPE_ENUM, TEXT_ALIGN_CENTER);\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<table> align attribute is obsolete.\");\n   }\n\n   if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"bgcolor\"))) {\n      bgcolor = a_Html_color_parse(html, attrbuf, -1);\n      if (bgcolor != -1)\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR,\n                                           CSS_TYPE_COLOR, bgcolor);\n      if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n         BUG_MSG(\"<table> bgcolor attribute is obsolete.\");\n   }\n\n   html->style (); // evaluate now, so we can build non-css hints for the cells\n\n   /* The style for the cells */\n   html->styleEngine->clearNonCssHints ();\n   if (border > 0) {\n      cssLength = CSS_CREATE_LENGTH (1, CSS_LENGTH_TYPE_PX);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_WIDTH,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_TOP_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_INSET);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_BOTTOM_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_INSET);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_LEFT_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_INSET);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_BORDER_RIGHT_STYLE,\n                                        CSS_TYPE_ENUM, BORDER_INSET);\n   }\n\n   if (cellpadding != -1) {\n      cssLength = CSS_CREATE_LENGTH (cellpadding, CSS_LENGTH_TYPE_PX);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_PADDING_TOP,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_PADDING_BOTTOM,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_PADDING_LEFT,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n      html->styleEngine->setNonCssHint (CSS_PROPERTY_PADDING_RIGHT,\n                                        CSS_TYPE_LENGTH_PERCENTAGE, cssLength);\n   }\n\n}\nvoid Html_tag_content_table(DilloHtml *html, const char *tag, int tagsize)\n{\n   dw::core::Widget *table;\n\n   HT2TB(html)->addParbreak (0, html->wordStyle ());\n   table = new dw::Table(prefs.limit_text_width);\n   HT2TB(html)->addWidget (table, html->style ());\n   HT2TB(html)->addParbreak (0, html->wordStyle ());\n\n   S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TOP;\n   S_TOP(html)->table_border_mode = DILLO_HTML_TABLE_BORDER_SEPARATE;\n   S_TOP(html)->cell_text_align_set = FALSE;\n   S_TOP(html)->table = table;\n\n}\n\n/*\n * <TR>\n */\nvoid Html_tag_open_tr(DilloHtml *html, const char *tag, int tagsize)\n{\n   const char *attrbuf;\n   int32_t bgcolor = -1;\n\n   html->styleEngine->inheritNonCssHints ();\n\n   switch (S_TOP(html)->table_mode) {\n   case DILLO_HTML_TABLE_MODE_NONE:\n      _MSG(\"Invalid HTML syntax: <tr> outside <table>\\n\");\n      return;\n\n   case DILLO_HTML_TABLE_MODE_TOP:\n   case DILLO_HTML_TABLE_MODE_TR:\n   case DILLO_HTML_TABLE_MODE_TD:\n\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"bgcolor\"))) {\n         bgcolor = a_Html_color_parse(html, attrbuf, -1);\n         if (bgcolor != -1)\n            html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR,\n                                              CSS_TYPE_COLOR, bgcolor);\n         if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n            BUG_MSG(\"<tr> bgcolor attribute is obsolete.\");\n      }\n\n      if (a_Html_get_attr (html, tag, tagsize, \"align\")) {\n         S_TOP(html)->cell_text_align_set = TRUE;\n         a_Html_tag_set_align_attr (html, tag, tagsize);\n      }\n\n      html->styleEngine->inheritBackgroundColor ();\n\n      if (bgcolor != -1) {\n         html->styleEngine->setNonCssHint(CSS_PROPERTY_BACKGROUND_COLOR,\n                                          CSS_TYPE_COLOR, bgcolor);\n      }\n      a_Html_tag_set_valign_attr (html, tag, tagsize);\n      break;\n   default:\n      break;\n   }\n}\n\nvoid Html_tag_content_tr(DilloHtml *html, const char *tag, int tagsize)\n{\n   switch (S_TOP(html)->table_mode) {\n   case DILLO_HTML_TABLE_MODE_NONE:\n      return;\n   case DILLO_HTML_TABLE_MODE_TOP:\n   case DILLO_HTML_TABLE_MODE_TR:\n   case DILLO_HTML_TABLE_MODE_TD:\n      ((dw::Table*)S_TOP(html)->table)->addRow (html->style ());\n   default:\n      break;\n   }\n\n   S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TR;\n}\n\n/*\n * <TD>\n */\nvoid Html_tag_open_td(DilloHtml *html, const char *tag, int tagsize)\n{\n   Html_tag_open_table_cell (html, tag, tagsize,\n                             dw::core::style::TEXT_ALIGN_LEFT);\n}\n\nvoid Html_tag_content_td(DilloHtml *html, const char *tag, int tagsize)\n{\n   Html_tag_content_table_cell (html, tag, tagsize);\n}\n\n/*\n * <tbody>\n */\nvoid Html_tag_open_tbody(DilloHtml *html, const char *tag, int tagsize)\n{\n}\n\nvoid Html_tag_content_tbody(DilloHtml *html, const char *tag, int tagsize)\n{\n}\n\n/*\n * <tfoot>\n */\nvoid Html_tag_open_tfoot(DilloHtml *html, const char *tag, int tagsize)\n{\n}\n\n/*\n * <thead>\n */\nvoid Html_tag_open_thead(DilloHtml *html, const char *tag, int tagsize)\n{\n}\n\n/*\n * <TH>\n */\nvoid Html_tag_open_th(DilloHtml *html, const char *tag, int tagsize)\n{\n   Html_tag_open_table_cell (html, tag, tagsize,\n                             dw::core::style::TEXT_ALIGN_CENTER);\n}\n\nvoid Html_tag_content_th(DilloHtml *html, const char *tag, int tagsize)\n{\n   Html_tag_content_table_cell (html, tag, tagsize);\n}\n\n/*\n * Utilities\n */\n\n/*\n * The table border model is stored in the table's stack item\n */\nstatic int Html_table_get_border_model(DilloHtml *html)\n{\n   static int i_TABLE = -1;\n   if (i_TABLE == -1)\n      i_TABLE = a_Html_tag_index(\"table\");\n\n   int s_idx = html->stack->size();\n   while (--s_idx > 0 && html->stack->getRef(s_idx)->tag_idx != i_TABLE)\n      ;\n   return html->stack->getRef(s_idx)->table_border_mode;\n}\n\n/*\n * Set current table's border model\n */\nstatic void Html_table_set_border_model(DilloHtml *html,\n                                        DilloHtmlTableBorderMode mode)\n{\n   int s_idx = html->stack->size(), i_TABLE = a_Html_tag_index(\"table\");\n\n   while (--s_idx > 0 && html->stack->getRef(s_idx)->tag_idx != i_TABLE) ;\n   if (s_idx > 0)\n      html->stack->getRef(s_idx)->table_border_mode = mode;\n}\n\n/* WORKAROUND: collapsing border model requires moving rendering code from\n *             the cell to the table, and making table-code aware of each\n *             cell style.\n * This workaround mimics collapsing model within separate model. This is not\n * a complete emulation but should be enough for most cases.\n */\nstatic void Html_set_collapsing_border_model(DilloHtml *html, Widget *col_tb)\n{\n   dw::core::style::Style *collapseStyle, *tableStyle;\n   dw::core::style::StyleAttrs collapseCellAttrs, collapseTableAttrs;\n   int borderWidth, marginWidth;\n\n   tableStyle = ((dw::Table*)S_TOP(html)->table)->getStyle ();\n   borderWidth = html->style ()->borderWidth.top;\n   marginWidth = tableStyle->margin.top;\n\n   collapseCellAttrs = *(html->style ());\n   collapseCellAttrs.margin.setVal (0);\n   collapseCellAttrs.borderWidth.left = 0;\n   collapseCellAttrs.borderWidth.top = 0;\n   collapseCellAttrs.borderWidth.right = borderWidth;\n   collapseCellAttrs.borderWidth.bottom = borderWidth;\n   collapseCellAttrs.hBorderSpacing = 0;\n   collapseCellAttrs.vBorderSpacing = 0;\n   collapseStyle = Style::create(&collapseCellAttrs);\n   col_tb->setStyle (collapseStyle);\n\n   if (Html_table_get_border_model(html) != DILLO_HTML_TABLE_BORDER_COLLAPSE) {\n      Html_table_set_border_model(html, DILLO_HTML_TABLE_BORDER_COLLAPSE);\n      collapseTableAttrs = *tableStyle;\n      collapseTableAttrs.margin.setVal (marginWidth);\n      collapseTableAttrs.borderWidth.left = borderWidth;\n      collapseTableAttrs.borderWidth.top = borderWidth;\n      collapseTableAttrs.borderWidth.right = 0;\n      collapseTableAttrs.borderWidth.bottom = 0;\n      collapseTableAttrs.hBorderSpacing = 0;\n      collapseTableAttrs.vBorderSpacing = 0;\n      collapseTableAttrs.borderColor = collapseCellAttrs.borderColor;\n      collapseTableAttrs.borderStyle = collapseCellAttrs.borderStyle;\n      /* CSS2 17.6.2: table does not have padding (in collapsing mode) */\n      collapseTableAttrs.padding.setVal (0);\n      collapseStyle = Style::create(&collapseTableAttrs);\n      ((dw::Table*)S_TOP(html)->table)->setStyle (collapseStyle);\n   }\n}\n\n/*\n * Adjust style for separate border model.\n * (Dw uses this model internally).\n */\nstatic void Html_set_separate_border_model(DilloHtml *html, Widget *col_tb)\n{\n   dw::core::style::Style *separateStyle;\n   dw::core::style::StyleAttrs separateCellAttrs;\n\n   separateCellAttrs = *(html->style ());\n   /* CSS2 17.5: Internal table elements do not have margins */\n   separateCellAttrs.margin.setVal (0);\n   separateStyle = Style::create(&separateCellAttrs);\n   col_tb->setStyle (separateStyle);\n}\n\n/*\n * used by <TD> and <TH>\n */\nstatic void Html_tag_open_table_cell(DilloHtml *html,\n                                     const char *tag, int tagsize,\n                                     dw::core::style::TextAlignType text_align)\n{\n   const char *attrbuf;\n   int32_t bgcolor;\n\n   html->styleEngine->inheritNonCssHints ();\n\n   switch (S_TOP(html)->table_mode) {\n   case DILLO_HTML_TABLE_MODE_NONE:\n      return;\n\n   case DILLO_HTML_TABLE_MODE_TOP:\n      /* a_Dw_table_add_cell takes care that dillo does not crash. */\n      /* continues */\n   case DILLO_HTML_TABLE_MODE_TR:\n   case DILLO_HTML_TABLE_MODE_TD:\n      /* text style */\n      if (!S_TOP(html)->cell_text_align_set) {\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_TEXT_ALIGN,\n                                           CSS_TYPE_ENUM, text_align);\n      }\n      if (a_Html_get_attr(html, tag, tagsize, \"nowrap\")) {\n         if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n            BUG_MSG(\"<t%c> nowrap attribute is obsolete.\",\n               (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');\n         html->styleEngine->setNonCssHint(CSS_PROPERTY_WHITE_SPACE,\n                                          CSS_TYPE_ENUM, WHITE_SPACE_NOWRAP);\n      }\n\n      a_Html_tag_set_align_attr (html, tag, tagsize);\n\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"width\"))) {\n         html->styleEngine->setNonCssHint (CSS_PROPERTY_WIDTH,\n                                           CSS_TYPE_LENGTH_PERCENTAGE,\n                                           a_Html_parse_length (html, attrbuf));\n         if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n            BUG_MSG(\"<t%c> width attribute is obsolete.\",\n               (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');\n      }\n\n      a_Html_tag_set_valign_attr (html, tag, tagsize);\n\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"bgcolor\"))) {\n         bgcolor = a_Html_color_parse(html, attrbuf, -1);\n         if (bgcolor != -1)\n            html->styleEngine->setNonCssHint (CSS_PROPERTY_BACKGROUND_COLOR,\n                                              CSS_TYPE_COLOR, bgcolor);\n         if (html->DocType == DT_HTML && html->DocTypeVersion >= 5.0f)\n            BUG_MSG(\"<t%c> bgcolor attribute is obsolete.\",\n               (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');\n      }\n\n   default:\n      /* compiler happiness */\n      break;\n   }\n}\n\nstatic void Html_tag_content_table_cell(DilloHtml *html,\n                                     const char *tag, int tagsize)\n{\n   int colspan = 1, rowspan = 1;\n   const char *attrbuf;\n   Widget *col_tb;\n\n   switch (S_TOP(html)->table_mode) {\n   case DILLO_HTML_TABLE_MODE_NONE:\n      BUG_MSG(\"<t%c> outside <table>.\",\n              (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');\n      return;\n\n   case DILLO_HTML_TABLE_MODE_TOP:\n      BUG_MSG(\"<t%c> outside <tr>.\",\n              (tagsize >=3 && (D_ASCII_TOLOWER(tag[2]) == 'd')) ? 'd' : 'h');\n      /* a_Dw_table_add_cell takes care that dillo does not crash. */\n      /* continues */\n   case DILLO_HTML_TABLE_MODE_TR:\n   case DILLO_HTML_TABLE_MODE_TD:\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"colspan\"))) {\n         char *invalid;\n         colspan = strtol(attrbuf, &invalid, 10);\n         if ((colspan < 0) || (attrbuf == invalid))\n            colspan = 1;\n      }\n      /* TODO: check errors? */\n      if ((attrbuf = a_Html_get_attr(html, tag, tagsize, \"rowspan\")))\n         rowspan = MAX(1, strtol (attrbuf, NULL, 10));\n      if (html->style ()->textAlign\n          == TEXT_ALIGN_STRING)\n         col_tb = new AlignedTableCell (\n                     ((dw::Table*)S_TOP(html)->table)->getCellRef (),\n                     prefs.limit_text_width);\n      else\n         col_tb = new SimpleTableCell (prefs.limit_text_width);\n\n      if (html->style()->borderCollapse == BORDER_MODEL_COLLAPSE){\n         Html_set_collapsing_border_model(html, col_tb);\n      } else {\n         Html_set_separate_border_model(html, col_tb);\n      }\n\n      ((dw::Table*)S_TOP(html)->table)->addCell (col_tb, colspan, rowspan);\n      S_TOP(html)->textblock = html->dw = col_tb;\n      break;\n\n   default:\n      /* compiler happiness */\n      break;\n   }\n\n   S_TOP(html)->table_mode = DILLO_HTML_TABLE_MODE_TD;\n}\n"
  },
  {
    "path": "src/table.hh",
    "content": "#ifndef __TABLE_HH__\n#define __TABLE_HH__\n\n/*\n * Classes\n */\n\nclass DilloHtml;\n\n/*\n * Table parsing functions\n */\n\nvoid Html_tag_open_table(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_content_table(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_open_tr(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_content_tr(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_open_td(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_content_td(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_open_tbody(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_content_tbody(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_open_tfoot(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_open_thead(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_open_th(DilloHtml *html, const char *tag, int tagsize);\nvoid Html_tag_content_th(DilloHtml *html, const char *tag, int tagsize);\n\n#endif /* __TABLE_HH__ */\n"
  },
  {
    "path": "src/timeout.cc",
    "content": "/*\n * File: timeout.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n// Simple ADT for timeout functions\n\n#include <FL/Fl.H>\n#include \"timeout.hh\"\n\n// C++ functions with C linkage ----------------------------------------------\n\n/*\n * Hook a one-time timeout function 'cb' after 't' seconds\n * with 'cbdata\" as its data.\n */\nvoid a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata)\n{\n   Fl::add_timeout(t, cb, cbdata);\n}\n\n/*\n * To be called from inside the 'cb' function when it wants to keep running\n */\nvoid a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata)\n{\n   Fl::add_timeout(t, cb, cbdata);\n}\n\n/*\n * Stop running a timeout function\n */\nvoid a_Timeout_remove()\n{\n   /* in FLTK, timeouts run one time by default */\n}\n\n"
  },
  {
    "path": "src/timeout.hh",
    "content": "#ifndef __TIMEOUT_HH__\n#define __TIMEOUT_HH__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\ntypedef void (*TimeoutCb_t)(void *data);\n\nvoid a_Timeout_add(float t, TimeoutCb_t cb, void *cbdata);\nvoid a_Timeout_repeat(float t, TimeoutCb_t cb, void *cbdata);\nvoid a_Timeout_remove();\n\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __TIMEOUT_HH__ */\n\n"
  },
  {
    "path": "src/tipwin.cc",
    "content": "/*\n * File: tipwin.cc\n *\n * Copyright 2012 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * The tipwin idea was derived from the Fl_Slider example [1]\n * by Greg Ercolano, which is in public domain.\n *\n * [1] http://seriss.com/people/erco/fltk/#SliderTooltip\n */\n\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <FL/fl_draw.H>\n#include <FL/Fl.H>\n#include <FL/Fl_Group.H>\n#include <FL/Fl_Menu_Window.H>\n#include <FL/Fl_Tooltip.H>\n#include <FL/Fl_Button.H>\n\n#include \"prefs.h\"\n#include \"tipwin.hh\"\n\n/*\n * Forward declarations\n */\nstatic void show_timeout(void*);\nstatic void recent_timeout(void*);\n\n/*\n * Custom tooltip window\n */\nTipWin::TipWin() : Fl_Menu_Window(1, 1)     // will autosize\n{\n   bgcolor = fl_color_cube(FL_NUM_RED - 1, FL_NUM_GREEN - 1, FL_NUM_BLUE - 2);\n   recent = 0;\n   tip[0] = '\\0';\n   cur_widget = NULL;\n   set_override(); // no border\n   end();\n}\n\nvoid TipWin::draw()\n{\n   draw_box(FL_BORDER_BOX, 0, 0, w(), h(), bgcolor);\n   fl_color(FL_BLACK);\n   fl_font(labelfont(), labelsize());\n   fl_draw(tip, 3, 3, w() - 6, h() - 6,\n           //Fl_Align(FL_ALIGN_LEFT | FL_ALIGN_WRAP));\n           Fl_Align(FL_ALIGN_LEFT));\n}\n\nvoid TipWin::value(const char *s) {\n   // Recalc size of window\n   snprintf(tip, sizeof(tip) - 1, \"%s\", s);\n   fl_font(labelfont(), labelsize());\n   int W = w(), H = h();\n   W = 0;\n   fl_measure(tip, W, H, 0);\n   W += 8; H += 8;\n   size(W, H);\n   redraw();\n}\n\nvoid TipWin::do_show(void *wid) {\n   cur_widget = wid;  // Keep track of requesting widget\n   if (prefs.show_ui_tooltip) {\n      Fl::add_timeout(recent ? 0.2f : 0.8f, show_timeout);\n   }\n}\n\nvoid TipWin::do_hide() {\n   Fl::remove_timeout(show_timeout);\n   if (shown()) {\n      hide();\n      recent = 1;\n      Fl::add_timeout(0.8f, recent_timeout);\n   }\n}\n\nvoid TipWin::recent_tooltip(int val) {\n   recent = val;\n}\n\n//--------------------------------------------------------------------------\n\nTipWin *my_tipwin(void)\n{\n   static TipWin *tw = NULL;\n\n   if (!tw) {\n      Fl_Group *save = Fl_Group::current();    // save current widget..\n      tw = new TipWin();                       // ..because this trashes it\n      tw->hide();                              // start hidden\n      Fl_Group::current(save);                 // ..then back to previous.\n   }\n   return tw;\n}\n\nstatic void show_timeout(void*) {\n  // if offscreen, move tip ABOVE mouse instead\n  int scr_x, scr_y, scr_w, scr_h;\n  Fl::screen_xywh(scr_x, scr_y, scr_w, scr_h);\n  int ty = Fl::event_y_root() + 20;\n  if (ty + my_tipwin()->h() > scr_h)\n     ty = Fl::event_y_root() - 20 - my_tipwin()->h();\n  if (ty < 0) ty = 0;\n\n  my_tipwin()->position(Fl::event_x_root(), ty);\n  my_tipwin()->show();\n  my_tipwin()->recent_tooltip(0);\n}\n\nstatic void recent_timeout(void*) {\n  my_tipwin()->recent_tooltip(0);\n}\n\n\n//---------------------------------------------------------------------------\n\n/*\n * A Button sharing a custom tooltip window\n */\nTipWinButton::TipWinButton(int x, int y, int w, int h, const char *l) :\n    Fl_Button(x, y, w, h, l)\n{\n   tipwin = my_tipwin();\n   mytooltip = strdup(\"empty\");\n}\n\nTipWinButton::~TipWinButton(void)\n{\n   tipwin->cancel(this); // cancel tooltip if shown\n   free(mytooltip);\n}\n\nint TipWinButton::handle(int e)\n{\n   switch (e) {\n   case FL_ENTER:\n      tipwin->value(mytooltip);\n      tipwin->do_show(this);\n      break;\n   case FL_PUSH:            // push mouse\n   case FL_RELEASE:         // release mouse\n   case FL_HIDE:            // widget goes away\n   case FL_LEAVE:           // leave focus\n      tipwin->do_hide();\n      break;\n   }\n   return (Fl_Button::handle(e));\n}\n\nvoid TipWinButton::set_tooltip(const char *s)\n{\n   free(mytooltip);\n   mytooltip = strdup(s);\n}\n\n\n//---------------------------------------------------------------------------\n\n/*\n * A Light Button sharing a custom tooltip window\n */\nCustButton::CustButton(int x, int y, int w, int h, const char *l) :\n   TipWinButton(x,y,w,h,l)\n{\n   norm_color = color();\n   light_color = PREFS_UI_BUTTON_HIGHLIGHT_COLOR;\n}\n\nint CustButton::handle(int e)\n{\n   if (active()) {\n      if (e == FL_ENTER) {\n         color(light_color);\n         redraw();\n      } else if (e == FL_LEAVE || e == FL_RELEASE || e == FL_HIDE) {\n         color(norm_color);\n         redraw();\n      }\n   } else if (e == FL_DEACTIVATE && color() != norm_color) {\n      color(norm_color);\n      redraw();\n   }\n   return TipWinButton::handle(e);\n}\n\nvoid CustButton::hl_color(Fl_Color col)\n{\n   light_color = col;\n}\n\n\n//---------------------------------------------------------------------------\n\n/*\n * An Input with custom tooltip window\n */\nTipWinInput::TipWinInput (int x, int y, int w, int h, const char *l) :\n   Fl_Input(x,y,w,h,l)\n{\n   tipwin = my_tipwin();\n   mytooltip = strdup(\"empty\");\n}\n\nTipWinInput::~TipWinInput(void)\n{\n   tipwin->cancel(this); // cancel tooltip if shown\n   free(mytooltip);\n}\n\nint TipWinInput::handle(int e)\n{\n   switch (e) {\n   case FL_ENTER:\n      tipwin->value(mytooltip);\n      tipwin->do_show(this);\n      break;\n   case FL_PUSH:            // push mouse\n   case FL_RELEASE:         // release mouse\n   case FL_HIDE:            // widget goes away\n   case FL_LEAVE:           // leave focus\n   case FL_KEYBOARD:        // key press\n      tipwin->do_hide();\n      break;\n   }\n   return (Fl_Input::handle(e));\n}\n\nvoid TipWinInput::set_tooltip(const char *s)\n{\n   free(mytooltip);\n   mytooltip = strdup(s);\n}\n\n"
  },
  {
    "path": "src/tipwin.hh",
    "content": "#ifndef __TIPWIN_HH__\n#define __TIPWIN_HH__\n\n#include <FL/Fl_Menu_Window.H>\n#include <FL/Fl_Button.H>\n#include <FL/Fl_Input.H>\n\n\n/*\n * Custom tooltip window\n */\nclass TipWin : public Fl_Menu_Window {\n   char tip[256];\n   int bgcolor, recent;\n   void *cur_widget;\npublic:\n   TipWin();\n   void draw();\n   void value(const char *s);\n   void do_show(void *wid);\n   void do_hide();\n   void recent_tooltip(int val);\n\n   void cancel(void *wid) {\n      if (wid == cur_widget) { cur_widget = NULL; do_hide(); }\n   }\n};\n\nextern TipWin *my_tipwin(void);\n\n\n/*\n * A Button sharing a custom tooltip window\n */\nclass TipWinButton : public Fl_Button {\n   char *mytooltip;\n   TipWin *tipwin;\n public:\n   TipWinButton(int x, int y, int w, int h, const char *l = 0);\n   ~TipWinButton();\n   virtual int handle(int e);\n\n   void set_tooltip(const char *s);\n};\n\n/*\n * A button that highlights on mouse over\n */\nclass CustButton : public TipWinButton {\n   Fl_Color norm_color, light_color;\npublic:\n   CustButton(int x, int y, int w, int h, const char *l=0);\n   virtual int handle(int e);\n   void hl_color(Fl_Color col);\n};\n\n\n/*\n * An Input with custom tooltip window\n */\nclass TipWinInput : public Fl_Input {\n   char *mytooltip;\n   TipWin *tipwin;\npublic:\n   TipWinInput (int x, int y, int w, int h, const char* l=0);\n   ~TipWinInput(void);\n   virtual int handle(int e);\n\n   void set_tooltip(const char *s);\n};\n\n\n#endif // __TIPWIN_HH__\n\n"
  },
  {
    "path": "src/ui.cc",
    "content": "/*\n * File: ui.cc\n *\n * Copyright (C) 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n// UI for Dillo\n\n#include <unistd.h>\n#include <stdio.h>\n#include <math.h>      /* rint */\n\n#include \"keys.hh\"\n#include \"ui.hh\"\n#include \"msg.h\"\n#include \"timeout.hh\"\n#include \"utf8.hh\"\n#include \"tipwin.hh\"\n\n#include <FL/Fl.H>\n#include <FL/Fl_Pixmap.H>\n#include <FL/Fl_Box.H>\n#include <FL/names.h>\n\n// Include image data\n#include \"pixmaps.h\"\n#include \"uicmd.hh\"\n#include \"history.h\"\n#include \"nav.h\"\n\nstruct iconset {\n   Fl_Image *ImgMeterOK, *ImgMeterBug,\n            *ImgHome, *ImgReload, *ImgSave, *ImgBook, *ImgTools,\n            *ImgClear,*ImgSearch, *ImgHelp, *ImgLeft, *ImgLeftIn,\n            *ImgRight, *ImgRightIn, *ImgStop, *ImgStopIn;\n};\n\nstatic struct iconset standard_icons = {\n   new Fl_Pixmap(mini_ok_xpm),\n   new Fl_Pixmap(mini_bug_xpm),\n   new Fl_Pixmap(home_xpm),\n   new Fl_Pixmap(reload_xpm),\n   new Fl_Pixmap(save_xpm),\n   new Fl_Pixmap(bm_xpm),\n   new Fl_Pixmap(tools_xpm),\n   new Fl_Pixmap(new_s_xpm),\n   new Fl_Pixmap(search_xpm),\n   new Fl_Pixmap(help_xpm),\n   new Fl_Pixmap(left_xpm),\n   NULL,\n   new Fl_Pixmap(right_xpm),\n   NULL,\n   new Fl_Pixmap(stop_xpm),\n   NULL,\n};\n\nstatic struct iconset small_icons = {\n   standard_icons.ImgMeterOK,\n   standard_icons.ImgMeterBug,\n   new Fl_Pixmap(home_s_xpm),\n   new Fl_Pixmap(reload_s_xpm),\n   new Fl_Pixmap(save_s_xpm),\n   new Fl_Pixmap(bm_s_xpm),\n   new Fl_Pixmap(tools_s_xpm),\n   new Fl_Pixmap(new_s_xpm),\n   standard_icons.ImgSearch,\n   standard_icons.ImgHelp,\n   new Fl_Pixmap(left_s_xpm),\n   NULL,\n   new Fl_Pixmap(right_s_xpm),\n   NULL,\n   new Fl_Pixmap(stop_s_xpm),\n   NULL,\n};\n\n\nstatic struct iconset *icons = &standard_icons;\n\n/*\n * Local sub classes\n */\n\n//----------------------------------------------------------------------------\n\n/*\n * (Used to avoid certain shortcuts in the location bar)\n */\nclass CustInput : public TipWinInput {\npublic:\n   CustInput (int x, int y, int w, int h, const char* l=0) :\n      TipWinInput(x,y,w,h,l) {};\n   virtual int handle(int e);\n};\n\n/*\n * Disable keys: Up, Down, Page_Up, Page_Down, Tab and\n * CTRL+{o,r,Home,End}  SHIFT+{Left,Right}.\n */\nint CustInput::handle(int e)\n{\n   int k = Fl::event_key();\n\n   _MSG(\"CustInput::handle event=%d\\n\", e);\n\n   // We're only interested in some flags\n   unsigned modifier = Fl::event_state() & (FL_SHIFT | FL_CTRL | FL_ALT);\n\n   // Don't focus with arrow keys\n   if (e == FL_FOCUS &&\n       (k == FL_Up || k == FL_Down || k == FL_Left || k == FL_Right)) {\n      return 0;\n   } else if (e == FL_KEYBOARD) {\n      if (k == FL_Escape && modifier == 0) {\n         // Let the parent group handle this Esc key\n         return 0;\n      } else if (modifier == FL_SHIFT) {\n         if (k == FL_Left || k == FL_Right) {\n            // Let these keys get to the UI\n            return 0;\n         }\n      } else if (modifier == FL_CTRL) {\n         if (k == 'A') {\n            position(size(), 0);\n            return 1;\n         } else if (k == 'a' || k == 'e') {\n            position(k == 'a' ? 0 : size());\n            return 1;\n         } else if (k == 'k') {\n            cut(position(), size());\n            return 1;\n         } else if (k == 'd') {\n            cut(position(), position()+1);\n            return 1;\n         } else if (k == 'l') {\n            // Make text selected when already focused.\n            position(size(), 0);\n            return 1;\n         } else if (k == 'h' || k == 'o' || k == 'r' ||\n                    k == FL_Home || k == FL_End) {\n            // Let these keys get to the UI\n            return 0;\n         }\n      } else if (modifier == 0) {\n         if (k == FL_Down || k == FL_Up ||\n             k == FL_Page_Down || k == FL_Page_Up || k == FL_Tab) {\n            // Give up focus and honor the key\n            a_UIcmd_focus_main_area(a_UIcmd_get_bw_by_widget(this));\n            return 0;\n         }\n      }\n      if (k == FL_Page_Down || k == FL_Page_Up) {\n         // These do nothing of interest when FL_MULTILINE_INPUT isn't set.\n         // Let them through for key commands.\n         return 0;\n      }\n   }\n\n   return TipWinInput::handle(e);\n}\n\n//----------------------------------------------------------------------------\n\n/*\n * Used to handle \"paste\" within the toolbar's Clear button.\n */\nclass CustPasteButton : public CustButton {\npublic:\n   CustPasteButton(int x, int y, int w, int h, const char *l=0) :\n      CustButton(x,y,w,h,l) {};\n   int handle(int e);\n};\n\nint CustPasteButton::handle(int e)\n{\n   if (e == FL_PASTE) {\n      const char* t = Fl::event_text();\n      if (t && *t) {\n         a_UIcmd_set_location_text(a_UIcmd_get_bw_by_widget(this), t);\n         a_UIcmd_open_urlstr(a_UIcmd_get_bw_by_widget(this), t);\n         return 1;\n      }\n   }\n   return CustButton::handle(e);\n}\n\n//----------------------------------------------------------------------------\n\n/*\n * Used to resize the progress boxes automatically.\n */\nclass CustProgressBox : public Fl_Box {\n   int padding;\npublic:\n   CustProgressBox(int x, int y, int w, int h, const char *l=0) :\n      Fl_Box(x,y,w,h,l) { padding = 0; };\n   void update_label(const char *lbl) {\n      int w = 0, h = 0;\n      if (!padding) {\n         copy_label(\"W\");\n         measure_label(w, h);\n         padding = w > 2 ? w/2 : 1;\n      }\n      copy_label(lbl);\n   }\n};\n\n//\n// Toolbar buttons -----------------------------------------------------------\n//\n//static const char *button_names[] = {\n//   \"Back\", \"Forward\", \"Home\", \"Reload\", \"Save\", \"Stop\", \"Bookmarks\", \"Tools\",\n//   \"Clear\", \"Search\"\n//};\n\n\n//\n// Callback functions --------------------------------------------------------\n//\n\n/*\n * Callback for the search button.\n */\nstatic void search_cb(Fl_Widget *wid, void *data)\n{\n   int b = Fl::event_button();\n\n   if (b == FL_LEFT_MOUSE) {\n      a_UIcmd_search_dialog(a_UIcmd_get_bw_by_widget(wid));\n   }\n}\n\n/*\n * Callback for the help button.\n */\nstatic void help_cb(Fl_Widget *w, void *)\n{\n   char *path = dStrconcat(DOC_PATH, \"/user_help.html\", NULL);\n   BrowserWindow *bw = a_UIcmd_get_bw_by_widget(w);\n\n   if (access(path, R_OK) == 0) {\n      char *urlstr = dStrconcat(\"file:\", path, NULL);\n      a_UIcmd_open_urlstr(bw, urlstr);\n      dFree(urlstr);\n   } else {\n      MSG(\"Can't read local help file at \\\"%s\\\".\"\n          \" Getting remote help...\\n\", path);\n      a_UIcmd_open_urlstr(bw, \"https://dillo-browser.github.io/old/dillo3-help.html\");\n   }\n   dFree(path);\n}\n\n/*\n * Callback for the File menu button.\n */\nstatic void filemenu_cb(Fl_Widget *wid, void *)\n{\n   int b = Fl::event_button();\n   if (b == FL_LEFT_MOUSE || b == FL_RIGHT_MOUSE) {\n      a_UIcmd_file_popup(a_UIcmd_get_bw_by_widget(wid), wid);\n   }\n}\n\n/*\n * Callback for the location's clear-button.\n */\nstatic void clear_cb(Fl_Widget *w, void *data)\n{\n   UI *ui = (UI*)data;\n\n   int b = Fl::event_button();\n   if (b == FL_LEFT_MOUSE) {\n      ui->set_location(\"\");\n      ui->focus_location();\n   } else if (b == FL_MIDDLE_MOUSE) {\n      ui->paste_url();\n   }\n}\n\n/*\n * Send the browser to the new URL in the location.\n */\nstatic void location_cb(Fl_Widget *wid, void *data)\n{\n   Fl_Input *i = (Fl_Input*)wid;\n   UI *ui = (UI*)data;\n\n   _MSG(\"location_cb()\\n\");\n   a_UIcmd_open_urlstr(a_UIcmd_get_bw_by_widget(i), i->value());\n\n   if (ui->temporaryPanels())\n      ui->panels_toggle();\n}\n\n\n/*\n * Callback handler for button press on the panel\n */\nstatic void b1_cb(Fl_Widget *wid, void *cb_data)\n{\n   int bn = VOIDP2INT(cb_data);\n   int b = Fl::event_button();\n   if (b >= FL_LEFT_MOUSE && b <= FL_RIGHT_MOUSE) {\n      _MSG(\"[%s], mouse button %d was pressed\\n\", button_names[bn], b);\n      _MSG(\"mouse button %d was pressed\\n\", b);\n   }\n   switch (bn) {\n   case UI_BACK:\n      if (b == FL_LEFT_MOUSE) {\n         a_UIcmd_back(a_UIcmd_get_bw_by_widget(wid));\n      } else if (b == FL_RIGHT_MOUSE) {\n         a_UIcmd_back_popup(a_UIcmd_get_bw_by_widget(wid), wid->x(),\n                            wid->y() + wid->h());\n      }\n      break;\n   case UI_FORW:\n      if (b == FL_LEFT_MOUSE) {\n         a_UIcmd_forw(a_UIcmd_get_bw_by_widget(wid));\n      } else if (b == FL_RIGHT_MOUSE) {\n         a_UIcmd_forw_popup(a_UIcmd_get_bw_by_widget(wid), wid->x(),\n                            wid->y() + wid->h());\n      }\n      break;\n   case UI_HOME:\n      if (b == FL_LEFT_MOUSE) {\n         a_UIcmd_home(a_UIcmd_get_bw_by_widget(wid));\n      }\n      break;\n   case UI_RELOAD:\n      if (b == FL_LEFT_MOUSE) {\n         a_UIcmd_reload(a_UIcmd_get_bw_by_widget(wid));\n      }\n      break;\n   case UI_SAVE:\n      if (b == FL_LEFT_MOUSE) {\n         a_UIcmd_save(a_UIcmd_get_bw_by_widget(wid));\n      }\n      break;\n   case UI_STOP:\n      if (b == FL_LEFT_MOUSE) {\n         a_UIcmd_stop(a_UIcmd_get_bw_by_widget(wid));\n      }\n      break;\n   case UI_BOOK:\n      if (b == FL_LEFT_MOUSE) {\n         a_UIcmd_book(a_UIcmd_get_bw_by_widget(wid));\n      }\n      break;\n   case UI_TOOLS:\n      if (b == FL_LEFT_MOUSE || b == FL_RIGHT_MOUSE) {\n         a_UIcmd_tools(a_UIcmd_get_bw_by_widget(wid), wid->x(),\n                       wid->y() + wid->h());\n      }\n      break;\n   case UI_NEW_TAB:\n      if (b == FL_LEFT_MOUSE || b == FL_RIGHT_MOUSE) {\n\t\t   a_UIcmd_open_url_nt(a_UIcmd_get_bw_by_widget(wid), NULL, 1);\n      }\n      break;\n   default:\n      break;\n   }\n}\n\n/*\n * Callback for the bug meter button.\n */\nstatic void bugmeter_cb(Fl_Widget *wid, void *data)\n{\n   int b = Fl::event_button();\n   if (b == FL_LEFT_MOUSE) {\n      a_UIcmd_view_page_bugs(a_UIcmd_get_bw_by_widget(wid));\n   } else if (b == FL_RIGHT_MOUSE) {\n      a_UIcmd_bugmeter_popup(a_UIcmd_get_bw_by_widget(wid));\n   }\n}\n\n//////////////////////////////////////////////////////////////////////////////\n// UI class methods\n//\n\n//----------------------------\n// Panel construction methods\n//----------------------------\n\n/*\n * Make a generic navigation button\n */\nCustButton *UI::make_button(const char *label, Fl_Image *img, Fl_Image *deimg,\n                            int b_n, int start)\n{\n   if (start)\n      p_xpos = 0;\n\n   CustButton *b = new CustButton(p_xpos, 0, bw, bh, (lbl) ? label : NULL);\n   if (img)\n      b->image(img);\n   if (deimg)\n      b->deimage(deimg);\n   b->callback(b1_cb, INT2VOIDP(b_n));\n   b->clear_visible_focus();\n   b->labelsize(12);\n   b->box(FL_FLAT_BOX);\n   b->down_box(FL_THIN_DOWN_FRAME);\n   p_xpos += bw;\n   return b;\n}\n\n/*\n * Create the archetipic browser buttons\n */\nvoid UI::make_toolbar(int tw, int th)\n{\n   if (!icons->ImgLeftIn) {\n      icons->ImgLeftIn = icons->ImgLeft->copy();\n      icons->ImgLeftIn->desaturate();\n      icons->ImgLeftIn->color_average(FL_BACKGROUND_COLOR, .14f);\n   }\n   if (!icons->ImgRightIn) {\n      icons->ImgRightIn = icons->ImgRight->copy();\n      icons->ImgRightIn->desaturate();\n      icons->ImgRightIn->color_average(FL_BACKGROUND_COLOR, .14f);\n   }\n   if (!icons->ImgStopIn) {\n      icons->ImgStopIn = icons->ImgStop->copy();\n      icons->ImgStopIn->desaturate();\n      icons->ImgStopIn->color_average(FL_BACKGROUND_COLOR, .14f);\n   }\n   Back = make_button(\"Back\", icons->ImgLeft, icons->ImgLeftIn, UI_BACK, 1);\n   Forw = make_button(\"Forw\", icons->ImgRight, icons->ImgRightIn, UI_FORW);\n   Home = make_button(\"Home\", icons->ImgHome, NULL, UI_HOME);\n   Reload = make_button(\"Reload\", icons->ImgReload, NULL, UI_RELOAD);\n   Stop = make_button(\"Stop\", icons->ImgStop, icons->ImgStopIn, UI_STOP);\n   Save = make_button(\"Save\", icons->ImgSave, NULL, UI_SAVE);\n   Bookmarks = make_button(\"Book\", icons->ImgBook, NULL, UI_BOOK);\n   Tools = make_button(\"Tools\", icons->ImgTools, NULL, UI_TOOLS);\n\n   Back->set_tooltip(\"Previous page\");\n   Forw->set_tooltip(\"Next page\");\n   Home->set_tooltip(\"Go to the Home page\");\n   Reload->set_tooltip(\"Reload\");\n   Save->set_tooltip(\"Save this page\");\n   Stop->set_tooltip(\"Stop loading\");\n   Bookmarks->set_tooltip(\"View bookmarks\");\n   Tools->set_tooltip(\"Settings\");\n}\n\n/*\n * Create the location box (Clear/Input/Search)\n */\nvoid UI::make_location(int ww)\n{\n   CustButton *b;\n\n    b = Clear = (CustButton*) new CustPasteButton(p_xpos,0,16,lh,0);\n    b->image(icons->ImgClear);\n    b->callback(clear_cb, this);\n    b->clear_visible_focus();\n    b->box(FL_THIN_UP_BOX);\n    b->set_tooltip(\"Clear the URL box.\\nMiddle-click to paste a URL.\");\n    p_xpos += b->w();\n\n    LocationGroup = new Fl_Group(p_xpos,0,ww-p_xpos-32,lh,0);\n    LocationGroup->begin();\n     CustInput *i = new CustInput(p_xpos,0,ww-p_xpos-32,lh,0);\n     Location = i;\n     i->when(FL_WHEN_ENTER_KEY);\n     i->callback(location_cb, this);\n     i->set_tooltip(\"Location\");\n     i->textsize((int) rint(14.0 * prefs.font_factor));\n     p_xpos += i->w();\n    LocationGroup->box(FL_THIN_UP_BOX);   // or FL_FLAT_BOX\n    LocationGroup->end();\n\n    Search = b = new CustButton(p_xpos,0,16,lh,0);\n    b->image(icons->ImgSearch);\n    b->callback(search_cb, this);\n    b->clear_visible_focus();\n    b->box(FL_THIN_UP_BOX);\n    b->set_tooltip(\"Search the Web\");\n    p_xpos += b->w();\n\n    Help = b = new CustButton(p_xpos,0,16,lh,0);\n    b->image(icons->ImgHelp);\n    b->callback(help_cb, this);\n    b->clear_visible_focus();\n    b->box(FL_THIN_UP_BOX);\n    b->set_tooltip(\"Help\");\n    p_xpos += b->w();\n\n}\n\n/*\n * Create the progress bars\n */\nvoid UI::make_progress_bars(int wide, int thin_up)\n{\n    // Images\n    IProg = new CustProgressBox(p_xpos,p_ypos,pw,bh);\n    IProg->labelsize(12);\n    IProg->box(thin_up ? FL_THIN_UP_BOX : FL_EMBOSSED_BOX);\n    IProg->update_label(wide ? \"Images\\n0 of 0\" : \"0 of 0\");\n    p_xpos += pw;\n    // Page\n    PProg = new CustProgressBox(p_xpos,p_ypos,pw,bh);\n    PProg->labelsize(12);\n    PProg->box(thin_up ? FL_THIN_UP_BOX : FL_EMBOSSED_BOX);\n    PProg->update_label(wide ? \"Page\\n0.0 KB\" : \"0.0 KB\");\n}\n\n/*\n * Create the \"File\" menu\n * Static function for File menu callbacks.\n */\nvoid UI::make_filemenu_button()\n{\n   CustButton *btn;\n   int w = 0, h = 0, padding;\n\n   FileButton = btn = new CustButton(p_xpos,0,bw,bh,\"W\");\n   btn->labeltype(FL_FREE_LABELTYPE);\n   btn->measure_label(w, h);\n   padding = w;\n   btn->copy_label(PanelSize == P_tiny ? \"&F\" : \"&File\");\n   btn->measure_label(w,h);\n   h = (PanelSize == P_tiny) ? bh : lh;\n   btn->size(w+padding, h);\n   p_xpos += btn->w();\n   _MSG(\"UI::make_filemenu_button w=%d h=%d padding=%d\\n\", w, h, padding);\n   btn->box(FL_THIN_UP_BOX);\n   btn->callback(filemenu_cb, this);\n   btn->set_tooltip(\"File menu\");\n   btn->clear_visible_focus();\n   if (!prefs.show_filemenu)\n      btn->hide();\n}\n\n\n/*\n * Create the control panel\n */\nvoid UI::make_panel(int ww)\n{\n   Fl_Widget *w;\n\n   if (Small_Icons)\n      icons = &small_icons;\n   else\n      icons = &standard_icons;\n\n   pw = 70;\n   p_xpos = p_ypos = 0;\n   if (PanelSize == P_tiny) {\n      if (Small_Icons)\n         bw = 22, bh = 22, mh = 0, lh = 22, lbl = 0;\n      else\n         bw = 28, bh = 28, mh = 0, lh = 28, lbl = 0;\n   } else if (PanelSize == P_small) {\n      if (Small_Icons)\n         bw = 20, bh = 20, mh = 0, lh = 20, lbl = 0;\n      else\n         bw = 28, bh = 28, mh = 0, lh = 28, lbl = 0;\n   } else if (PanelSize == P_medium) {\n      if (Small_Icons)\n         bw = 42, bh = 36, mh = 0, lh = 22, lbl = 1;\n      else\n         bw = 45, bh = 45, mh = 0, lh = 28, lbl = 1;\n   }\n   nh = bh, fh = 28; sh = 20;\n\n   current(0);\n   if (PanelSize == P_tiny) {\n      NavBar = new CustGroupHorizontal(0,0,ww,nh);\n      NavBar->box(FL_NO_BOX);\n      NavBar->begin();\n       make_filemenu_button();\n       make_toolbar(ww,bh);\n       make_location(ww);\n       NavBar->resizable(LocationGroup);\n       make_progress_bars(0,1);\n      NavBar->box(FL_THIN_UP_FRAME);\n      NavBar->end();\n      NavBar->rearrange();\n      TopGroup->insert(*NavBar,0);\n   } else {\n       // Location\n       LocBar = new CustGroupHorizontal(0,0,ww,lh);\n       LocBar->box(FL_NO_BOX);\n       LocBar->begin();\n        p_xpos = 0;\n        make_filemenu_button();\n        make_location(ww);\n        LocBar->resizable(LocationGroup);\n       LocBar->end();\n       LocBar->rearrange();\n       TopGroup->insert(*LocBar,0);\n\n       // Toolbar\n       p_ypos = 0;\n       NavBar = new CustGroupHorizontal(0,0,ww,bh);\n       NavBar->box(FL_NO_BOX);\n       NavBar->begin();\n        make_toolbar(ww,bh);\n        w = new Fl_Box(p_xpos,0,ww-p_xpos-2*pw,bh);\n        w->box(FL_FLAT_BOX);\n        NavBar->resizable(w);\n        p_xpos = ww - 2*pw;\n        if (PanelSize == P_small) {\n           make_progress_bars(0,0);\n        } else {\n           make_progress_bars(1,0);\n        }\n       NavBar->end();\n       NavBar->rearrange();\n       TopGroup->insert(*NavBar,1);\n   }\n}\n\n/*\n * Create the status panel\n */\nvoid UI::make_status_bar(int ww, int wh)\n{\n   const int bm_w = 20;\n   StatusBar = new CustGroupHorizontal(0, wh-sh, ww, sh);\n   StatusBar->box(FL_NO_BOX);\n\n    // Status box\n    StatusOutput = new Fl_Output(0, wh-sh, ww-bm_w, sh);\n    StatusOutput->value(\"https://dillo-browser.github.io/old/\");\n    StatusOutput->labelsize(8);\n    StatusOutput->box(FL_THIN_DOWN_BOX);\n    StatusOutput->clear_visible_focus();\n    StatusOutput->color(FL_BACKGROUND_COLOR);\n\n    // Bug Meter\n    BugMeter = new CustButton(ww-bm_w,wh-sh,bm_w,sh);\n    BugMeter->image(icons->ImgMeterOK);\n    BugMeter->box(FL_THIN_DOWN_BOX);\n    BugMeter->align(FL_ALIGN_INSIDE | FL_ALIGN_TEXT_NEXT_TO_IMAGE);\n    BugMeter->set_tooltip(\"Show HTML bugs\\n(right-click for menu)\");\n    BugMeter->callback(bugmeter_cb, this);\n    BugMeter->clear_visible_focus();\n\n   StatusBar->end();\n   StatusBar->resizable(StatusOutput);\n   StatusBar->rearrange();\n}\n\n/*\n * User Interface constructor\n */\nUI::UI(int x, int y, int ui_w, int ui_h, const char* label, const UI *cur_ui) :\n  CustGroupVertical(x, y, ui_w, ui_h, label)\n{\n   LocBar = NavBar = StatusBar = NULL;\n\n   Tabs = NULL;\n   TopGroup = this;\n   TopGroup->box(FL_NO_BOX);\n   clear_flag(SHORTCUT_LABEL);\n\n   PanelTemporary = false;\n   if (cur_ui) {\n      PanelSize = cur_ui->PanelSize;\n      Small_Icons = cur_ui->Small_Icons;\n      Panelmode = cur_ui->Panelmode;\n   } else {\n     // Set some default values\n     PanelSize = prefs.panel_size;\n     Small_Icons = prefs.small_icons;\n     Panelmode = (prefs.fullwindow_start) ? UI_HIDDEN : UI_NORMAL;\n   }\n\n   // Control panel\n   TopGroup->begin();\n    make_panel(ui_w);\n\n    // Render area\n    Main = new Fl_Group(0,0,0,0,\"Welcome...\"); // size is set by rearrange()\n    Main->align(FL_ALIGN_CENTER|FL_ALIGN_INSIDE);\n    Main->box(FL_FLAT_BOX);\n    Main->labelfont(FL_HELVETICA_BOLD_ITALIC);\n    Main->labelsize(36);\n    Main->labeltype(FL_SHADOW_LABEL);\n    TopGroup->add(Main);\n    TopGroup->resizable(Main);\n    MainIdx = TopGroup->find(Main);\n\n    // Find text bar\n    FindBar = new Findbar(ui_w, fh);\n    TopGroup->add(FindBar);\n\n    // Status Panel\n    make_status_bar(ui_w, ui_h);\n    TopGroup->add(StatusBar);\n   TopGroup->end();\n   TopGroup->rearrange();\n\n   customize();\n\n   if (Panelmode == UI_HIDDEN) {\n      panels_toggle();\n   }\n}\n\n/*\n * UI destructor\n */\nUI::~UI()\n{\n   _MSG(\"UI::~UI()\\n\");\n}\n\n/*\n * FLTK event handler for this window.\n */\nint UI::handle(int event)\n{\n   _MSG(\"UI::handle event=%s\\n\", fl_eventnames[event]);\n\n   int ret = 0;\n   if (event == FL_KEYBOARD) {\n      return 0; // Receive as shortcut\n   } else if (event == FL_SHORTCUT) {\n      KeysCommand_t cmd = Keys::getKeyCmd();\n      if (cmd == KEYS_NOP) {\n         // Do nothing\n      } else if (cmd == KEYS_SCREEN_UP || cmd == KEYS_SCREEN_DOWN ||\n                 cmd == KEYS_SCREEN_LEFT || cmd == KEYS_SCREEN_RIGHT ||\n                 cmd == KEYS_LINE_UP || cmd == KEYS_LINE_DOWN ||\n                 cmd == KEYS_LEFT || cmd == KEYS_RIGHT ||\n                 cmd == KEYS_TOP || cmd == KEYS_BOTTOM) {\n         a_UIcmd_scroll(a_UIcmd_get_bw_by_widget(this), cmd);\n         ret = 1;\n      } else if (cmd == KEYS_BACK) {\n         a_UIcmd_back(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_FORWARD) {\n         a_UIcmd_forw(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_BOOKMARKS) {\n         a_UIcmd_book(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_FIND) {\n         findbar_toggle(1);\n         ret = 1;\n      } else if (cmd == KEYS_WEBSEARCH) {\n         a_UIcmd_search_dialog(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_GOTO) {\n         focus_location();\n         ret = 1;\n      } else if (cmd == KEYS_HIDE_PANELS) {\n         /* Hide findbar if present, hide panels if not */\n         (FindBar->visible()) ? findbar_toggle(0) : panels_toggle();\n         temporaryPanels(false);\n         ret = 1;\n      } else if (cmd == KEYS_OPEN) {\n         a_UIcmd_open_file(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_HOME) {\n         a_UIcmd_home(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_RELOAD) {\n         a_UIcmd_reload(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_STOP) {\n         a_UIcmd_stop(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_SAVE) {\n         a_UIcmd_save(a_UIcmd_get_bw_by_widget(this));\n         ret = 1;\n      } else if (cmd == KEYS_FILE_MENU) {\n         a_UIcmd_file_popup(a_UIcmd_get_bw_by_widget(this), FileButton);\n         ret = 1;\n      } else if (cmd == KEYS_VIEW_SOURCE) {\n         BrowserWindow *bw = a_UIcmd_get_bw_by_widget(this);\n         const DilloUrl *url = a_History_get_url(NAV_TOP_UIDX(bw));\n         a_UIcmd_view_page_source(bw, url);\n         ret = 1;\n      }\n   } else if (event == FL_RELEASE) {\n      if (Fl::event_button() == FL_MIDDLE_MOUSE &&\n          prefs.middle_click_drags_page == 0) {\n         /* nobody claimed the event; try paste */\n         paste_url();\n         ret = 1;\n      }\n   }\n\n   if (!ret) {\n      ret = Fl_Group::handle(event);\n   }\n   if (!ret && event == FL_PUSH && !prefs.middle_click_drags_page) {\n      /* nobody claimed FL_PUSH: ask for FL_RELEASE,\n       * which is necessary for middle-click paste URL) */\n      ret = 1;\n   }\n\n   return ret;\n}\n\n\n//----------------------------\n// API for the User Interface\n//----------------------------\n\n/*\n * Get the text from the location input-box.\n */\nconst char *UI::get_location()\n{\n   return Location->value();\n}\n\n/*\n * Set a new URL in the location input-box.\n */\nvoid UI::set_location(const char *str)\n{\n   if (!str) str = \"\";\n   Location->value(str);\n   Location->position((Fl::focus() == Location) ? strlen(str) : 0);\n}\n\n/*\n * Focus location entry.\n * If it's not visible, show it until the callback is done.\n */\nvoid UI::focus_location()\n{\n   if (Panelmode == UI_HIDDEN) {\n      panels_toggle();\n      temporaryPanels(true);\n   }\n   Location->take_focus();\n   // Make text selected when already focused.\n   Location->position(Location->size(), 0);\n}\n\n/*\n * Focus Main area.\n */\nvoid UI::focus_main()\n{\n   Main->take_focus();\n}\n\n/*\n * Set a new message in the status bar.\n */\nvoid UI::set_status(const char *str)\n{\n   StatusOutput->value(str);\n}\n\n/*\n * Set the page progress text\n * cmd: 0 Deactivate, 1 Update, 2 Clear\n */\nvoid UI::set_page_prog(size_t nbytes, int cmd)\n{\n   char str[32];\n\n   if (cmd == 0) {\n      PProg->deactivate();\n   } else {\n      PProg->activate();\n      if (cmd == 1) {\n         char prefix;\n         float magnitude;\n\n         if (nbytes >= 1024 * 1024) {\n            prefix = 'M';\n            magnitude = nbytes / (1024 * 1024.0);\n         } else {\n            prefix = 'K';\n            magnitude = nbytes / 1024.0;\n         }\n         snprintf(str, 32, \"%s%.1f %cB\",\n                  (PanelSize == 0) ? \"\" : \"Page\\n\", magnitude, prefix);\n      } else if (cmd == 2) {\n         str[0] = '\\0';\n      }\n      PProg->update_label(str);\n   }\n}\n\n/*\n * Set the image progress text\n * cmd: 0 Deactivate, 1 Update, 2 Clear\n */\nvoid UI::set_img_prog(int n_img, int t_img, int cmd)\n{\n   char str[32];\n\n   if (cmd == 0) {\n      IProg->deactivate();\n   } else {\n      IProg->activate();\n      if (cmd == 1) {\n         snprintf(str, 32, \"%s%d of %d\",\n                  (PanelSize == 0) ? \"\" : \"Images\\n\", n_img, t_img);\n      } else if (cmd == 2) {\n         str[0] = '\\0';\n      }\n      IProg->update_label(str);\n   }\n}\n\n/*\n * Set the bug meter progress text\n */\nvoid UI::set_bug_prog(int n_bug)\n{\n   char str[32];\n   int new_w = 20;\n\n   if (n_bug == 0) {\n      BugMeter->image(icons->ImgMeterOK);\n      BugMeter->label(\"\");\n   } else if (n_bug >= 1) {\n      if (n_bug == 1)\n         BugMeter->image(icons->ImgMeterBug);\n      snprintf(str, 32, \"%d\", n_bug);\n      BugMeter->copy_label(str);\n      new_w = strlen(str)*9 + 20;\n   }\n   BugMeter->size(new_w, BugMeter->h());\n   StatusBar->rearrange();\n}\n\n/*\n * Customize the UI's panel (show/hide buttons)\n */\nvoid UI::customize()\n{\n   if ( !prefs.show_back )\n      Back->hide();\n   if ( !prefs.show_forw )\n      Forw->hide();\n   if ( !prefs.show_home )\n      Home->hide();\n   if ( !prefs.show_reload )\n      Reload->hide();\n   if ( !prefs.show_save )\n      Save->hide();\n   if ( !prefs.show_stop )\n      Stop->hide();\n   if ( !prefs.show_bookmarks )\n      Bookmarks->hide();\n   if ( !prefs.show_tools )\n      Tools->hide();\n   if ( !prefs.show_clear_url )\n      Clear->hide();\n   if ( !prefs.show_url )\n      Location->hide();\n   if ( !prefs.show_search )\n      Search->hide();\n   if ( !prefs.show_help )\n      Help->hide();\n   if ( !prefs.show_progress_box ) {\n      IProg->hide();\n      PProg->hide();\n   }\n\n   if (NavBar)\n      NavBar->rearrange();\n   if (LocBar)\n      LocBar->rearrange();\n}\n\n/*\n * On-the-fly panel style change\n */\nvoid UI::change_panel(int new_size, int small_icons)\n{\n   char *loc_text = dStrdup(Location->value());\n\n   // Remove current panel's bars\n   init_sizes();\n   TopGroup->remove(LocBar);\n   Fl::delete_widget(LocBar);\n   TopGroup->remove(NavBar);\n   Fl::delete_widget(NavBar);\n   LocBar = NavBar = NULL;\n\n   // Set internal vars for panel size\n   PanelSize = new_size;\n   Small_Icons = small_icons;\n\n   // make a new panel\n   make_panel(TopGroup->w());\n   customize();\n   a_UIcmd_set_buttons_sens(a_UIcmd_get_bw_by_widget(this));\n\n   TopGroup->rearrange();\n   Location->value(loc_text);\n   Location->take_focus();\n\n   dFree(loc_text);\n}\n\n/*\n * Set 'nw' as the main render area widget\n */\nvoid UI::set_render_layout(Fl_Group *nw)\n{\n   // Resize layout widget to current height\n   nw->resize(0,Main->y(),Main->w(),Main->h());\n\n   TopGroup->insert(*nw, Main);\n   remove(Main);\n   delete(Main);\n   Main = nw;\n   TopGroup->resizable(Main);\n}\n\n/*\n * Set button sensitivity (Back/Forw/Stop)\n */\nvoid UI::button_set_sens(UIButton btn, int sens)\n{\n   switch (btn) {\n   case UI_BACK:\n      (sens) ? Back->activate() : Back->deactivate();\n      break;\n   case UI_FORW:\n      (sens) ? Forw->activate() : Forw->deactivate();\n      break;\n   case UI_STOP:\n      (sens) ? Stop->activate() : Stop->deactivate();\n      break;\n   default:\n      break;\n   }\n}\n\n/*\n * Paste a middle-click-selection into \"Clear\" button as URL\n */\nvoid UI::paste_url()\n{\n   Fl::paste(*Clear, false);\n}\n\n/*\n * Adjust space for the findbar (if necessary) and show or remove it\n */\nvoid UI::findbar_toggle(bool add)\n{\n   /* WORKAROUND:\n    * This is tricky: As fltk-1.3 resizes hidden widgets (which it\n    * doesn't resize when visible!). We need to set the size to (0,0) to\n    * get the desired behaviour.\n    * (STR#2639 in FLTK bug tracker).\n    */\n\n   if (add) {\n      if (!FindBar->visible())\n         FindBar->size(w(), fh);\n      FindBar->show();\n   } else {\n      // hide\n      FindBar->size(0,0);\n      FindBar->hide();\n      // reset state\n      a_UIcmd_findtext_reset(a_UIcmd_get_bw_by_widget(this));\n      // focus main area\n      focus_main();\n   }\n   TopGroup->rearrange();\n}\n\n/*\n * Make panels disappear growing the render area.\n * WORKAROUND: here we avoid hidden widgets resize by setting their\n *             size to (0,0) while hidden.\n *             (Already reported to FLTK team)\n */\nvoid UI::panels_toggle()\n{\n   int hide = StatusBar->visible();\n\n   // hide/show panels\n   init_sizes();\n   if (LocBar) {\n      hide ? LocBar->size(0,0) : LocBar->size(w(),lh);\n      hide ? LocBar->hide() : LocBar->show();\n   }\n   if (NavBar) {\n      hide ? NavBar->size(0,0) : NavBar->size(w(),nh);\n      hide ? NavBar->hide() : NavBar->show();\n   }\n   if (StatusBar) {\n      hide ? StatusBar->size(0,0) : StatusBar->size(w(),sh);\n      hide ? StatusBar->hide() : StatusBar->show();\n      StatusBar->rearrange();\n   }\n\n   TopGroup->rearrange();\n   Panelmode = (hide) ? UI_HIDDEN : UI_NORMAL;\n}\n"
  },
  {
    "path": "src/ui.hh",
    "content": "#ifndef __UI_HH__\n#define __UI_HH__\n\n// UI for dillo --------------------------------------------------------------\n\n#include <FL/Fl_Window.H>\n#include <FL/Fl_Widget.H>\n#include <FL/Fl_Button.H>\n#include <FL/Fl_Input.H>\n#include <FL/Fl_Output.H>\n#include <FL/Fl_Image.H>\n#include <FL/Fl_Tabs.H>\n\n#include \"tipwin.hh\"\n#include \"findbar.hh\"\n\ntypedef enum {\n   UI_BACK = 0,\n   UI_FORW,\n   UI_HOME,\n   UI_RELOAD,\n   UI_SAVE,\n   UI_STOP,\n   UI_BOOK,\n   UI_TOOLS,\n   UI_CLEAR,\n   UI_SEARCH,\n   UI_NEW_TAB\n} UIButton;\n\ntypedef enum {\n   UI_NORMAL = 0,     /* make sure it's compatible with bool */\n   UI_HIDDEN = 1\n} UIPanelmode;\n\n\n// Min size to fit the full UI\n#define UI_MIN_W 600\n#define UI_MIN_H 200\n\n// Private classes\nclass CustProgressBox;\nclass CustTabs;\n\n\n// Class definitions ---------------------------------------------------------\n/*\n * Used to reposition group's widgets when some of them are hidden.\n * All children get the height of the group but retain their original width.\n * The resizable child get's the remaining space.\n */\nclass CustGroupHorizontal : public Fl_Group {\n   Fl_Widget *rsz;\npublic:\n  CustGroupHorizontal(int x,int y,int w ,int h,const char *l = 0) :\n    Fl_Group(x,y,w,h,l) { };\n\n  void rearrange() {\n     Fl_Widget*const* a = array();\n     int sum = 0, _x = x();\n     int children_ = children();\n\n     if (resizable())\n        rsz = resizable();\n\n     for (int i=0; i < children_; i++)\n        if (a[i] != resizable() && a[i]->visible())\n           sum += a[i]->w();\n\n     for (int i=0; i < children_; i++) {\n        if (a[i] == rsz) {\n           if (w() > sum) {\n              a[i]->resize(_x, y(), w()-sum, h());\n              if (!resizable())\n                 resizable(rsz);\n           } else {\n              /* widgets overflow width */\n              a[i]->resize(_x, y(), 0, h());\n              resizable(NULL);\n           }\n        } else {\n           a[i]->resize(_x, y(), a[i]->w(), h());\n        }\n        if (a[i]->visible())\n           _x += a[i]->w();\n     }\n     init_sizes();\n     redraw();\n  }\n};\n\nclass CustGroupVertical : public Fl_Group {\npublic:\n  CustGroupVertical(int x,int y,int w ,int h,const char *l = 0) :\n    Fl_Group(x,y,w,h,l) { };\n\n  void rearrange() {\n     Fl_Widget*const* a = array();\n     int sum = 0, _y = y();\n     int children_ = children();\n\n     for (int i=0; i < children_; i++)\n        if (a[i] != resizable() && a[i]->visible())\n           sum += a[i]->h();\n\n     for (int i=0; i < children_; i++) {\n        if (a[i] == resizable()) {\n           a[i]->resize(x(), _y, w(), h() - sum);\n        } else {\n           a[i]->resize(x(), _y, w(), a[i]->h());\n        }\n        if (a[i]->visible())\n           _y += a[i]->h();\n     }\n     init_sizes();\n     redraw();\n  }\n};\n\n\n//\n// UI class definition -------------------------------------------------------\n//\nclass UI : public CustGroupVertical {\n   CustTabs *Tabs;\n\n   CustGroupVertical *TopGroup;\n   CustButton *Back, *Forw, *Home, *Reload, *Save, *Stop, *Bookmarks,\n              *Tools, *Clear, *Search, *Help, *BugMeter, *FileButton, *NewTab;\n   CustGroupHorizontal *LocBar, *NavBar, *StatusBar;\n   Fl_Input *Location;\n   CustProgressBox *PProg, *IProg;\n   Fl_Group *Panel, *Main, *LocationGroup;\n   Fl_Output *StatusOutput;\n   Findbar *FindBar;\n\n   int MainIdx;\n   // Panel customization variables\n   int PanelSize, Small_Icons;\n   int p_xpos, p_ypos, bw, bh, mh, lh, nh, fh, sh, pw, lbl;\n   bool PanelTemporary;\n\n   UIPanelmode Panelmode;\n   CustButton *make_button(const char *label, Fl_Image *img, Fl_Image*deimg,\n                           int b_n, int start = 0);\n   void make_toolbar(int tw, int th);\n   void make_location(int ww);\n   void make_progress_bars(int wide, int thin_up);\n   void make_menubar(int x, int y, int w, int h);\n   void make_filemenu_button();\n   void make_panel(int ww);\n   void make_status_bar(int ww, int wh);\n\npublic:\n\n   UI(int x,int y,int w,int h, const char* label = 0, const UI *cur_ui=NULL);\n   ~UI();\n\n   // To manage what events to catch and which to let pass\n   int handle(int event);\n\n   const char *get_location();\n   void set_location(const char *str);\n   void focus_location();\n   void focus_main();\n   void set_status(const char *str);\n   void set_page_prog(size_t nbytes, int cmd);\n   void set_img_prog(int n_img, int t_img, int cmd);\n   void set_bug_prog(int n_bug);\n   void set_render_layout(Fl_Group *nw);\n   void customize();\n   void button_set_sens(UIButton btn, int sens);\n   void paste_url();\n   int get_panelsize() { return PanelSize; }\n   int get_smallicons() { return Small_Icons; }\n   void change_panel(int new_size, int small_icons);\n   void findbar_toggle(bool add);\n   void panels_toggle();\n\n   CustTabs *tabs() { return Tabs; }\n   void tabs(CustTabs *tabs) { Tabs = tabs; }\n   bool temporaryPanels() { return PanelTemporary; }\n   void temporaryPanels(bool val) { PanelTemporary = val; }\n\n   // Hooks to method callbacks\n   void color_change_cb_i();\n   void toggle_cb_i();\n};\n\n#endif // __UI_HH__\n"
  },
  {
    "path": "src/uicmd.cc",
    "content": "/*\n * File: uicmd.cc\n *\n * Copyright (C) 2005-2011 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n// Functions/Methods for commands triggered from the UI\n\n\n#include <string.h>\n#include <stdio.h>\n#include <stdarg.h>\n#include <stdlib.h>     /* for qsort */\n#include <math.h>       /* for rint */\n#include <limits.h>     /* for UINT_MAX */\n#include <sys/stat.h>\n\n#include <FL/Fl.H>\n#include <FL/Fl_Widget.H>\n#include <FL/Fl_Double_Window.H>\n#include <FL/Fl_Wizard.H>\n#include <FL/Fl_Box.H>\n#include <FL/Fl_Pack.H>\n#include <FL/Fl_Scroll.H>\n#include <FL/names.h>\n\n#include \"paths.hh\"\n#include \"keys.hh\"\n#include \"ui.hh\"\n#include \"uicmd.hh\"\n#include \"timeout.hh\"\n#include \"utf8.hh\"\n#include \"menu.hh\"\n#include \"dialog.hh\"\n#include \"xembed.hh\"\n#include \"bookmark.h\"\n#include \"history.h\"\n#include \"msg.h\"\n#include \"prefs.h\"\n#include \"misc.h\"\n\n#include \"dw/fltkviewport.hh\"\n\n#include \"nav.h\"\n\n//#define DEFAULT_TAB_LABEL \"-.untitled.-\"\n#define DEFAULT_TAB_LABEL \"-.new.-\"\n\n// Handy macro\n#define BW2UI(bw) ((UI*)((bw)->ui))\n\n// Platform idependent part\nusing namespace dw::core;\n// FLTK related\nusing namespace dw::fltk;\n\n\n/*\n * Local data\n */\nstatic const char *save_dir = \"\";\n\n/*\n * Forward declarations\n */\nstatic BrowserWindow *UIcmd_tab_new(CustTabs *tabs, UI *old_ui, int focus);\nstatic void close_tab_btn_cb (Fl_Widget *w, void *cb_data);\nstatic char *UIcmd_make_search_str(const char *str);\nstatic void UIcmd_set_window_labels(Fl_Window *win, const char *str);\n\n//----------------------------------------------------------------------------\n\n/*\n * CustTabs ---------------------------------------------------------------\n */\n\n/*\n * stores the respective UI pointer\n */\nclass CustTabButton : public Fl_Button {\n   UI *ui_;\n   uint_t focus_num_; // Used to choose which tab to focus when closing the\n                      // active one (the highest numbered gets focus).\npublic:\n   CustTabButton (int x,int y,int w,int h, const char* label = 0) :\n      Fl_Button (x,y,w,h,label) { ui_ = NULL; focus_num_ = 0; };\n   void ui(UI *pui) { ui_ = pui; }\n   UI *ui(void) { return ui_; }\n   void focus_num(uint_t fn) { focus_num_ = fn; }\n   uint_t focus_num(void) { return focus_num_; }\n};\n\nstatic int btn_cmp(const void *p1, const void *p2)\n{\n   return ((*(CustTabButton * const *)p1)->focus_num() -\n           (*(CustTabButton * const *)p2)->focus_num() );\n}\n\n/*\n * Allows fine control of the tabbed interface\n */\nclass CustTabs : public Fl_Group {\n   uint_t focus_counter; // An increasing counter\n   int tab_w, tab_h, ctab_h, btn_w, ctl_w;\n   Fl_Wizard *Wizard;\n   Fl_Scroll *Scroll;\n   Fl_Pack *Pack;\n   Fl_Group *Control;\n   CustButton *CloseBtn;\n\n   void update_pack_offset(void);\n   void resize(int x, int y, int w, int h)\n      { Fl_Group::resize(x,y,w,h); update_pack_offset(); }\n   int get_btn_idx(UI *ui);\n   void increase_focus_counter() {\n      if (focus_counter < UINT_MAX) /* check for overflow */\n         ++focus_counter;\n      else {\n         /* wrap & normalize old numbers here */\n         CustTabButton **btns = dNew(CustTabButton*, num_tabs());\n         for (int i = 0; i < num_tabs(); ++i)\n            btns[i] = (CustTabButton*)Pack->child(i);\n         qsort(btns, num_tabs(), sizeof(CustTabButton *), btn_cmp);\n         focus_counter = 0;\n         for (int i = 0; i < num_tabs(); ++i)\n            btns[i]->focus_num(focus_counter++);\n         dFree(btns);\n      }\n   }\n\npublic:\n   CustTabs (int ww, int wh, int th, const char *lbl=0) :\n      Fl_Group(0,0,ww,th,lbl) {\n      Pack = NULL;\n      focus_counter = 0;\n      tab_w = 50, tab_h = th, ctab_h = 1, btn_w = 20, ctl_w = 1*btn_w+2;\n      resize(0,0,ww,ctab_h);\n      /* tab buttons go inside a pack within a scroll */\n      Scroll = new Fl_Scroll(0,0,ww-ctl_w,ctab_h);\n      Scroll->type(0); /* no scrollbars */\n      Scroll->box(FL_NO_BOX);\n       Pack = new Fl_Pack(0,0,ww-ctl_w,tab_h);\n       Pack->type(Fl_Pack::HORIZONTAL);\n       Pack->box(FL_NO_BOX); //FL_THIN_DOWN_FRAME\n       Pack->end();\n      Scroll->end();\n      resizable(Scroll);\n\n      /* control buttons go inside a group */\n      Control = new Fl_Group(ww-ctl_w,0,ctl_w,ctab_h);\n       CloseBtn = new CustButton(ww-ctl_w+2,0,btn_w,ctab_h, \"X\");\n       CloseBtn->box(FL_THIN_UP_BOX);\n       CloseBtn->clear_visible_focus();\n       CloseBtn->set_tooltip(prefs.right_click_closes_tab ?\n          \"Close current tab.\\nor Right-click tab label to close.\" :\n          \"Close current tab.\\nor Middle-click tab label to close.\");\n       CloseBtn->callback(close_tab_btn_cb, this);\n       CloseBtn->hide();\n      Control->end();\n\n      box(FL_FLAT_BOX);\n      end();\n\n      Wizard = new Fl_Wizard(0,ctab_h,ww,wh-ctab_h);\n      Wizard->box(FL_NO_BOX);\n      Wizard->end();\n   };\n   int handle(int e);\n   UI *add_new_tab(UI *old_ui, int focus);\n   void remove_tab(UI *ui);\n   Fl_Wizard *wizard(void) { return Wizard; }\n   int num_tabs() { return (Pack ? Pack->children() : 0); }\n   void switch_tab(CustTabButton *cbtn);\n   void prev_tab(void);\n   void next_tab(void);\n   void set_tab_label(UI *ui, const char *title);\n};\n\n/*\n * Callback for mouse click\n */\nstatic void tab_btn_cb (Fl_Widget *w, void *cb_data)\n{\n   CustTabButton *btn = (CustTabButton*) w;\n   CustTabs *tabs = (CustTabs*) cb_data;\n   int b = Fl::event_button();\n\n   if (b == FL_LEFT_MOUSE) {\n      tabs->switch_tab(btn);\n   } else if ((b == FL_RIGHT_MOUSE && prefs.right_click_closes_tab) ||\n              (b == FL_MIDDLE_MOUSE && !prefs.right_click_closes_tab)) {\n      // TODO: just an example, not necessarily final\n      a_UIcmd_close_bw(a_UIcmd_get_bw_by_widget(btn->ui()));\n   }\n}\n\n/*\n * Callback for the close-tab button\n */\nstatic void close_tab_btn_cb (Fl_Widget *, void *cb_data)\n{\n   CustTabs *tabs = (CustTabs*) cb_data;\n   int b = Fl::event_button();\n\n   if (b == FL_LEFT_MOUSE) {\n      a_UIcmd_close_bw(a_UIcmd_get_bw_by_widget(tabs->wizard()->value()));\n   }\n}\n\nint CustTabs::handle(int e)\n{\n   int ret = 0;\n\n   _MSG(\"CustTabs::handle e=%s\\n\", fl_eventnames[e]);\n   if (e == FL_KEYBOARD) {\n      return 0; // Receive as shortcut\n   } else if (e == FL_SHORTCUT) {\n      UI *ui = (UI*)wizard()->value();\n      BrowserWindow *bw = a_UIcmd_get_bw_by_widget(ui);\n      KeysCommand_t cmd = Keys::getKeyCmd();\n      if (cmd == KEYS_NOP) {\n         // Do nothing\n         _MSG(\"CustTabs::handle KEYS_NOP\\n\");\n      } else if (cmd == KEYS_NEW_TAB) {\n         a_UIcmd_open_url_nt(bw, NULL, 1);\n         ret = 1;\n      } else if (cmd == KEYS_CLOSE_TAB) {\n         a_UIcmd_close_bw(bw);\n         ret = 1;\n      } else if (cmd == KEYS_LEFT_TAB) {\n         prev_tab();\n         ret = 1;\n      } else if (cmd == KEYS_RIGHT_TAB) {\n         next_tab();\n         ret = 1;\n      } else if (cmd == KEYS_NEW_WINDOW) {\n         a_UIcmd_open_url_nw(bw, NULL);\n         ret = 1;\n      } else if (cmd == KEYS_CLOSE_ALL) {\n         a_Timeout_add(0.0, a_UIcmd_close_all_bw, NULL);\n         ret = 1;\n      }\n   }\n\n   return (ret) ? ret : Fl_Group::handle(e);\n}\n\n/*\n * Create a new tab with its own UI\n */\nUI *CustTabs::add_new_tab(UI *old_ui, int focus)\n{\n   if (num_tabs() == 1) {\n      // Show tabbar\n      ctab_h = tab_h;\n      Wizard->resize(0,ctab_h,Wizard->w(),window()->h()-ctab_h);\n      resize(0,0,window()->w(),ctab_h);    // tabbar\n      CloseBtn->show();\n      {int w = 0, h; Pack->child(0)->measure_label(w, h);\n       Pack->child(0)->size(w+14,ctab_h);}\n      Pack->child(0)->show(); // first tab button\n      window()->init_sizes();\n   }\n\n   /* The UI is constructed in a comfortable fitting size, and then resized\n    * so FLTK doesn't get confused later with even smaller dimensions! */\n   current(0);\n   UI *new_ui = new UI(0,0,UI_MIN_W,UI_MIN_H,\"Dillo:\",old_ui);\n   new_ui->resize(0,ctab_h,Wizard->w(),Wizard->h());\n   new_ui->tabs(this);\n   Wizard->add(new_ui);\n   new_ui->show();\n\n   CustTabButton *btn = new CustTabButton(num_tabs()*tab_w,0,tab_w,ctab_h);\n   btn->align(FL_ALIGN_INSIDE);\n   btn->labelsize(btn->labelsize()-2);\n   btn->copy_label(DEFAULT_TAB_LABEL);\n   btn->clear_visible_focus();\n   btn->box(FL_GTK_THIN_UP_BOX);\n   btn->color(focus ? PREFS_UI_TAB_ACTIVE_BG_COLOR : PREFS_UI_TAB_BG_COLOR);\n   btn->labelcolor(focus ? PREFS_UI_TAB_ACTIVE_FG_COLOR:PREFS_UI_TAB_FG_COLOR);\n   btn->ui(new_ui);\n   btn->callback(tab_btn_cb, this);\n   Pack->add(btn); // append\n\n   if (focus) {\n      switch_tab(btn);\n   } else {        // no focus\n      // set focus counter\n      increase_focus_counter();\n      btn->focus_num(focus_counter);\n\n      if (num_tabs() == 2) {\n         // tabbar added: redraw current page\n         Wizard->redraw();\n      }\n   }\n   if (num_tabs() == 1)\n      btn->hide();\n   update_pack_offset();\n\n   return new_ui;\n}\n\n/*\n * Remove tab by UI\n */\nvoid CustTabs::remove_tab(UI *ui)\n{\n   CustTabButton *btn;\n\n   // get active tab idx\n   int act_idx = get_btn_idx((UI*)Wizard->value());\n   // get to-be-removed tab idx\n   int rm_idx = get_btn_idx(ui);\n   btn = (CustTabButton*)Pack->child(rm_idx);\n\n   if (act_idx == rm_idx) {\n      // Active tab is being closed, switch to the \"previous\" one\n      CustTabButton *fbtn = NULL;\n      for (int i = 0; i < num_tabs(); ++i) {\n         if (i != rm_idx) {\n            if (!fbtn)\n               fbtn = (CustTabButton*)Pack->child(i);\n            CustTabButton *btn = (CustTabButton*)Pack->child(i);\n            if (btn->focus_num() > fbtn->focus_num())\n               fbtn = btn;\n         }\n      }\n      switch_tab(fbtn);\n   }\n   Pack->remove(rm_idx);\n   update_pack_offset();\n   delete btn;\n\n   Wizard->remove(ui);\n   delete(ui);\n\n   if (num_tabs() == 1) {\n      // hide tabbar\n      ctab_h = 1;\n      CloseBtn->hide();\n      Pack->child(0)->size(0,0);\n      Pack->child(0)->hide(); // first tab button\n      resize(0,0,window()->w(),ctab_h);    // tabbar\n      Wizard->resize(0,ctab_h,Wizard->w(),window()->h()-ctab_h);\n      window()->init_sizes();\n      window()->redraw();\n   }\n}\n\nint CustTabs::get_btn_idx(UI *ui)\n{\n   for (int i = 0; i < num_tabs(); ++i) {\n      CustTabButton *btn = (CustTabButton*)Pack->child(i);\n      if (btn->ui() == ui)\n         return i;\n   }\n   return -1;\n}\n\n/*\n * Keep active tab visible\n * (Pack children have unusable x() coordinate)\n */\nvoid CustTabs::update_pack_offset()\n{\n   dReturn_if (num_tabs() == 0);\n\n   // get active tab button\n   int act_idx = get_btn_idx((UI*)Wizard->value());\n   CustTabButton *cbtn = (CustTabButton*)Pack->child(act_idx);\n\n   // calculate tab button's x() coordinates\n   int x_i = 0, x_f;\n   for (int j=0; j < act_idx; ++j)\n      x_i += Pack->child(j)->w();\n   x_f = x_i + cbtn->w();\n\n   int scr_x = Scroll->xposition(), scr_y = Scroll->yposition();\n   int px_i = x_i - scr_x;\n   int px_f = px_i + cbtn->w();\n   int pw = Scroll->window()->w() - ctl_w;\n   _MSG(\"  scr_x=%d btn_x=%d px_i=%d btn_w=%d px_f=%d pw=%d\",\n       Scroll->xposition(),cbtn->x(),px_i,cbtn->w(),px_f,pw);\n   if (px_i < 0) {\n      Scroll->scroll_to(x_i, scr_y);\n   } else if (px_i > pw || (px_i > 0 && px_f > pw)) {\n      Scroll->scroll_to(MIN(x_i, x_f-pw), scr_y);\n   }\n   Scroll->redraw();\n   _MSG(\" >>scr_x=%d btn0_x=%d\\n\", scr_x, Pack->child(0)->x());\n}\n\n/*\n * Make cbtn's tab the active one\n */\nvoid CustTabs::switch_tab(CustTabButton *cbtn)\n{\n   int idx;\n   CustTabButton *btn;\n   BrowserWindow *bw;\n   UI *old_ui = (UI*)Wizard->value();\n\n   if (cbtn && cbtn->ui() != old_ui) {\n      // Set old tab label to normal color\n      if ((idx = get_btn_idx(old_ui)) != -1) {\n         btn = (CustTabButton*)Pack->child(idx);\n         btn->color(PREFS_UI_TAB_BG_COLOR);\n         btn->labelcolor(PREFS_UI_TAB_FG_COLOR);\n         btn->redraw();\n      }\n      /* We make a point of calling show() before value() is changed because\n       * the wizard may hide the old one before showing the new one. In that\n       * case, the new UI gets focus with Fl::e_keysym set to whatever\n       * triggered the switch, and this is a problem when it's Tab/Left/Right/\n       * Up/Down because some widgets (notably Fl_Group and Fl_Input) exhibit\n       * unwelcome behaviour in that case. If the new widgets are already\n       * shown, fl_fix_focus will fix everything with Fl::e_keysym temporarily\n       * cleared.\n       */\n      cbtn->ui()->show();\n      Wizard->value(cbtn->ui());\n      cbtn->color(PREFS_UI_TAB_ACTIVE_BG_COLOR);\n      cbtn->labelcolor(PREFS_UI_TAB_ACTIVE_FG_COLOR);\n      cbtn->redraw();\n      update_pack_offset();\n\n      // Update window title\n      if ((bw = a_UIcmd_get_bw_by_widget(cbtn->ui()))) {\n         const char *title = (cbtn->ui())->label();\n         UIcmd_set_window_labels(cbtn->window(), title ? title : \"\");\n      }\n      // Update focus priority\n      increase_focus_counter();\n      cbtn->focus_num(focus_counter);\n   }\n}\n\nvoid CustTabs::prev_tab()\n{\n   int idx;\n\n   if ((idx = get_btn_idx((UI*)Wizard->value())) != -1)\n      switch_tab((CustTabButton*)Pack->child(idx>0 ? idx-1 : num_tabs()-1));\n}\n\nvoid CustTabs::next_tab()\n{\n   int idx;\n\n   if ((idx = get_btn_idx((UI*)Wizard->value())) != -1)\n      switch_tab((CustTabButton*)Pack->child((idx+1<num_tabs()) ? idx+1 : 0));\n}\n\n/*\n * Set this UI's tab button label\n */\nvoid CustTabs::set_tab_label(UI *ui, const char *label)\n{\n   char title[128];\n   int idx = get_btn_idx(ui);\n\n   if (idx != -1) {\n      // Make a label for this tab\n      size_t tab_chars = 15, label_len = strlen(label);\n\n      if (label_len > tab_chars)\n         tab_chars = a_Utf8_end_of_char(label, tab_chars - 1) + 1;\n      snprintf(title, tab_chars + 1, \"%s\", label);\n      if (label_len > tab_chars)\n         snprintf(title + tab_chars, 4, \"...\");\n\n      // Avoid unnecessary redraws\n      if (strcmp(Pack->child(idx)->label(), title)) {\n         int w = 0, h;\n         Pack->child(idx)->copy_label(title);\n         Pack->child(idx)->measure_label(w, h);\n         Pack->child(idx)->size(w+14,ctab_h);\n         update_pack_offset();\n      }\n   }\n}\n\n\n//----------------------------------------------------------------------------\n\nstatic void win_cb (Fl_Widget *w, void *cb_data) {\n   CustTabs *tabs = (CustTabs*) cb_data;\n   int choice = 1, ntabs = tabs->num_tabs();\n\n   if (Fl::event_key() == FL_Escape) {\n      // Don't let FLTK close a browser window due to unhandled Escape\n      // (most likely with modifiers).\n      return;\n   }\n\n   if (prefs.show_quit_dialog && ntabs > 1)\n      choice = a_Dialog_choice(\"Dillo: Close window?\",\n                               \"Window contains more than one tab.\",\n                               \"Close\", \"Cancel\", NULL);\n   if (choice == 1)\n      while (ntabs-- > 0)\n         a_UIcmd_close_bw(a_UIcmd_get_bw_by_widget(tabs->wizard()->value()));\n}\n\n/*\n * Given a UI or UI child widget, return its bw.\n */\nBrowserWindow *a_UIcmd_get_bw_by_widget(void *v_wid)\n{\n   BrowserWindow *bw;\n   for (int i = 0; i < a_Bw_num(); ++i) {\n      bw = a_Bw_get(i);\n      if (((UI*)bw->ui)->contains((Fl_Widget*)v_wid)) {\n         return bw;\n      }\n   }\n   return NULL;\n}\n\n/*\n * Create a new UI and its associated BrowserWindow data structure.\n * Use style from v_ui. If non-NULL it must be of type UI*.\n */\nBrowserWindow *a_UIcmd_browser_window_new(int ww, int wh,\n                                          uint32_t xid, const void *vbw)\n{\n   BrowserWindow *old_bw = (BrowserWindow*)vbw;\n   BrowserWindow *new_bw = NULL;\n   UI *old_ui = old_bw ? BW2UI(old_bw) : NULL;\n   Fl_Window *win;\n\n   if (ww <= 0 || wh <= 0) {\n      // Set default geometry from dillorc.\n      ww = prefs.width;\n      wh = prefs.height;\n   }\n\n   if (xid)\n      win = new Xembed(xid, ww, wh);\n   else if (prefs.buffered_drawing != 2)\n      win = new Fl_Window(ww, wh);\n   else\n      win = new Fl_Double_Window(ww, wh);\n\n   win->box(FL_NO_BOX);\n   CustTabs *DilloTabs = new CustTabs(ww, wh, 16);\n   win->end();\n   win->resizable(DilloTabs->wizard());\n\n   int focus = 1;\n   new_bw = UIcmd_tab_new(DilloTabs, old_ui, focus);\n   win->show();\n\n   if (old_bw == NULL && prefs.xpos >= 0 && prefs.ypos >= 0) {\n      // position the first window according to preferences\n      DilloTabs->window()->position(prefs.xpos, prefs.ypos);\n   }\n\n   win->callback(win_cb, DilloTabs);\n\n   return new_bw;\n}\n\n/*\n * Set the window name and icon name.\n */\nstatic void UIcmd_set_window_labels(Fl_Window *win, const char *str)\n{\n   const char *copy;\n\n   win->Fl_Widget::copy_label(str);\n   copy = win->label();\n   win->label(copy, copy);\n}\n\n/*\n * Create a new Tab button, UI and its associated BrowserWindow data\n * structure.\n */\nstatic BrowserWindow *UIcmd_tab_new(CustTabs *tabs, UI *old_ui, int focus)\n{\n   _MSG(\" UIcmd_tab_new\\n\");\n\n   // Create and set the UI\n   UI *new_ui = tabs->add_new_tab(old_ui, focus);\n\n   // Now create the Dw render layout and viewport\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n   style::Color *bgColor = style::Color::create (layout, prefs.bg_color);\n   layout->setBgColor (bgColor);\n   layout->setBgImage (NULL, style::BACKGROUND_REPEAT,\n                       style::BACKGROUND_ATTACHMENT_SCROLL,\n                       style::createPerLength (0), style::createPerLength (0));\n\n   // set_render_layout() sets the proper viewport size\n   FltkViewport *viewport = new FltkViewport (0, 0, 0, 1);\n   viewport->box(FL_NO_BOX);\n   viewport->setBufferedDrawing (prefs.buffered_drawing ? true : false);\n   viewport->setDragScroll (prefs.middle_click_drags_page ? true : false);\n   layout->attachView (viewport);\n   new_ui->set_render_layout(viewport);\n   viewport->setScrollStep((int) rint(28.0 * prefs.font_factor));\n\n   // Now, create a new browser window structure\n   BrowserWindow *new_bw = a_Bw_new();\n\n   // Reference the UI from the bw\n   new_bw->ui = (void *)new_ui;\n   // Copy the layout pointer into the bw data\n   new_bw->render_layout = (void*)layout;\n\n   // Clear the window title\n   if (focus)\n      UIcmd_set_window_labels(new_ui->window(), new_ui->label());\n\n   // WORKAROUND: see findbar_toggle()\n   new_ui->findbar_toggle(0);\n\n   return new_bw;\n}\n\n/*\n * Close one browser window\n */\nvoid a_UIcmd_close_bw(void *vbw)\n{\n   BrowserWindow *bw = (BrowserWindow *)vbw;\n   UI *ui = BW2UI(bw);\n   CustTabs *tabs = ui->tabs();\n   Layout *layout = (Layout*)bw->render_layout;\n\n   _MSG(\"a_UIcmd_close_bw\\n\");\n   a_Bw_stop_clients(bw, BW_Root + BW_Img + BW_Force);\n   delete(layout);\n   if (tabs) {\n      tabs->remove_tab(ui);\n      if (tabs->num_tabs() == 0)\n         delete tabs->window();\n   }\n   a_Bw_free(bw);\n}\n\n/*\n * Close all the browser windows\n */\nvoid a_UIcmd_close_all_bw(void *)\n{\n   BrowserWindow *bw;\n   int choice = 1;\n\n   if (prefs.show_quit_dialog && a_Bw_num() > 1)\n      choice = a_Dialog_choice(\"Dillo: Quit?\",\n                               \"More than one open tab or window.\",\n                               \"Quit\", \"Cancel\", NULL);\n   if (choice == 1)\n      while ((bw = a_Bw_get(0)))\n         a_UIcmd_close_bw((void*)bw);\n}\n\n/*\n * Return a search string of the suffix if str starts with a\n * prefix of a search engine name and a blank\n */\nstatic char *UIcmd_find_search_str(const char *str)\n{\n   int p;\n   char *url = NULL;\n   int len = strcspn(str, \" \");\n\n   if (len > 0 && str[len] != '\\0') {\n      /* we found a ' ' in str, check whether the first part of str\n       * is a prefix of a search_url label\n       */\n      for (p = 0; p < dList_length(prefs.search_urls); p++) {\n         const char *search =\n            (const char *)dList_nth_data(prefs.search_urls, p);\n         if (search && strncasecmp(str, search, len) == 0) {\n            prefs.search_url_idx = p;\n            url = UIcmd_make_search_str(str + len + 1);\n            return url;\n            break;\n         }\n      }\n\n      prefs.search_url_idx = 0;\n      url = UIcmd_make_search_str(str);\n   }\n\n   return url;\n}\n\n/*\n * Open a new URL in the given browser window.\n *\n * our custom \"file:\" URIs are normalized here too.\n */\nvoid a_UIcmd_open_urlstr(void *vbw, const char *urlstr)\n{\n   char *new_urlstr;\n   char *search_urlstr = NULL;\n   DilloUrl *url;\n   int ch;\n   BrowserWindow *bw = (BrowserWindow*)vbw;\n\n   if ((search_urlstr = UIcmd_find_search_str(urlstr))) {\n      urlstr = search_urlstr;\n   }\n   if (urlstr && *urlstr) {\n      /* Filter URL string */\n      new_urlstr = a_Url_string_strip_delimiters(urlstr);\n\n      if (!dStrnAsciiCasecmp(new_urlstr, \"file:\", 5)) {\n         /* file URI */\n         ch = new_urlstr[5];\n         if (!ch || ch == '.') {\n            url = a_Url_new(Paths::getOldWorkingDir(), \"file:\");\n         } else if (ch == '~') {\n            url = a_Url_new(dGethomedir(), \"file:\");\n         } else {\n            url = a_Url_new(new_urlstr, \"file:\");\n         }\n\n      } else {\n         /* common case */\n         url = a_Url_new(new_urlstr, NULL);\n      }\n      dFree(new_urlstr);\n\n      if (url) {\n         a_UIcmd_open_url(bw, url);\n         a_Url_free(url);\n      }\n   }\n   dFree(search_urlstr);\n}\n\n/*\n * Open a new URL in the given browser window\n */\nvoid a_UIcmd_open_url(BrowserWindow *bw, const DilloUrl *url)\n{\n   if (url) {\n      a_Nav_push(bw, url, NULL);\n      BW2UI(bw)->focus_main();\n   } else {\n      // Used to start a bw with a blank screen\n      BW2UI(bw)->focus_location();\n      a_UIcmd_set_buttons_sens(bw);\n   }\n}\n\nstatic void UIcmd_open_url_nbw(BrowserWindow *new_bw, const DilloUrl *url)\n{\n   /* When opening a new BrowserWindow (tab or real window) we focus\n    * Location if we don't yet have an URL, main otherwise.\n    */\n   if (url) {\n      a_Nav_push(new_bw, url, NULL);\n      a_UIcmd_set_location_text(new_bw, URL_STR(url));\n      BW2UI(new_bw)->focus_main();\n   } else {\n      BW2UI(new_bw)->focus_location();\n      a_UIcmd_set_buttons_sens(new_bw);\n   }\n}\n\n/*\n * Open a new URL in a new browser window\n */\nvoid a_UIcmd_open_url_nw(BrowserWindow *bw, const DilloUrl *url)\n{\n   int w, h;\n   BrowserWindow *new_bw;\n\n   a_UIcmd_get_wh(bw, &w, &h);\n   new_bw = a_UIcmd_browser_window_new(w, h, 0, bw);\n\n   UIcmd_open_url_nbw(new_bw, url);\n}\n\n/*\n * Open a new URL in a new tab in the same browser window\n */\nvoid a_UIcmd_open_url_nt(void *vbw, const DilloUrl *url, int focus)\n{\n   BrowserWindow *bw = (BrowserWindow *)vbw;\n   BrowserWindow *new_bw = UIcmd_tab_new(BW2UI(bw)->tabs(),\n                                         bw ? BW2UI(bw) : NULL, focus);\n   UIcmd_open_url_nbw(new_bw, url);\n}\n\n/*\n * Send the browser back to previous page\n */\nvoid a_UIcmd_back(void *vbw)\n{\n   a_Nav_back((BrowserWindow*)vbw);\n}\n\n/*\n * Popup the navigation menu of the Back button\n */\nvoid a_UIcmd_back_popup(void *vbw, int x, int y)\n{\n   a_Menu_history_popup((BrowserWindow*)vbw, x, y, -1);\n}\n\n/*\n * Send the browser to next page in the history list\n */\nvoid a_UIcmd_forw(void *vbw)\n{\n   a_Nav_forw((BrowserWindow*)vbw);\n}\n\n/*\n * Popup the navigation menu of the Forward button\n */\nvoid a_UIcmd_forw_popup(void *vbw, int x, int y)\n{\n   a_Menu_history_popup((BrowserWindow*)vbw, x, y, 1);\n}\n\n/*\n * Send the browser to home URL\n */\nvoid a_UIcmd_home(void *vbw)\n{\n   a_UIcmd_open_url((BrowserWindow*)vbw, prefs.home);\n}\n\n/*\n * Reload current URL\n */\nvoid a_UIcmd_reload(void *vbw)\n{\n   a_Nav_reload((BrowserWindow*)vbw);\n}\n\n/*\n * Repush current URL\n */\nvoid a_UIcmd_repush(void *vbw)\n{\n   a_Nav_repush((BrowserWindow*)vbw);\n}\n\n/*\n * Zero-delay URL redirection.\n */\nvoid a_UIcmd_redirection0(void *vbw, const DilloUrl *url)\n{\n   a_Nav_redirection0((BrowserWindow*)vbw, url);\n}\n\n/*\n * Return a suitable filename for a given URL path.\n */\nstatic char *UIcmd_make_save_filename(const DilloUrl *url)\n{\n   size_t MaxLen = 64;\n   const char *dir = save_dir, *path, *path2, *query;\n   char *name, *free1, *free2, *n1, *n2;\n\n   free1 = free2 = NULL;\n\n   /* get the last component of the path */\n   path = URL_PATH(url);\n   path2 = strrchr(path, '/');\n   path = path2 ? path2 + 1 : path;\n\n   /* truncate the path if necessary */\n   if (strlen(path) > MaxLen) {\n      path = free1 = dStrndup(path, MaxLen);\n   }\n\n   /* is there a query? */\n   query = URL_QUERY(url);\n   if (*query) {\n      /* truncate the query if necessary */\n      if (strlen(query) > MaxLen) {\n         query = free2 = dStrndup(query, MaxLen);\n      }\n      name = dStrconcat(dir, path, \"?\", query, NULL);\n   } else {\n      name = dStrconcat(dir, path, NULL);\n   }\n\n   dFree(free1);\n   dFree(free2);\n\n   /* Replace %20 and ' ' with '_' */\n   for (n1 = n2 = name; *n1; n1++, n2++) {\n      *n2 =\n         (n1[0] == ' ')\n         ? '_' :\n         (n1[0] == '%' && n1[1] == '2' && n1[2] == '0')\n         ? (n1 += 2, '_') :\n         n1[0];\n   }\n   *n2 = 0;\n\n   return name;\n}\n\n/*\n * Set the default directory for saving files.\n */\nvoid a_UIcmd_init(void)\n{\n   const char *dir = prefs.save_dir;\n\n   if (dir && *dir) {\n      // assert a trailing '/'\n      save_dir =\n         (dir[strlen(dir)-1] == '/')\n         ? dStrdup(dir)\n         : dStrconcat(dir, \"/\", NULL);\n   }\n}\n\n/*\n * Check a file to save to.\n */\nstatic int UIcmd_save_file_check(const char *name)\n{\n   struct stat ss;\n   if (stat(name, &ss) == 0) {\n      Dstr *ds;\n      int ch;\n      ds = dStr_sized_new(128);\n      dStr_sprintf(ds,\n                  \"The file: %s (%d Bytes) already exists. What do we do?\",\n                   name, (int)ss.st_size);\n      ch = a_Dialog_choice(\"Dillo Save: File exists!\", ds->str,\n                           \"Abort\", \"Continue\", \"Rename\", NULL);\n      dStr_free(ds, 1);\n      return ch;\n   } else {\n      return 2; /* assume the file does not exist, so Continue */\n   }\n}\n\n/*\n * Save a URL\n */\nstatic void UIcmd_save(BrowserWindow *bw, const DilloUrl *url,\n                       const char *title)\n{\n   char *SuggestedName = UIcmd_make_save_filename(url);\n\n   while (1) {\n      const char *name = a_Dialog_save_file(title, NULL, SuggestedName);\n      dFree(SuggestedName);\n\n      if (name) {\n         switch (UIcmd_save_file_check(name)) {\n         case 0:\n         case 1:\n            /* Abort */\n            return;\n         case 2:\n            /* Continue */\n            MSG(\"UIcmd_save: %s\\n\", name);\n            a_Nav_save_url(bw, url, name);\n            return;\n         default:\n            /* Rename */\n            break; /* prompt again */\n         }\n      } else {\n         return; /* no name, so Abort */\n      }\n\n      SuggestedName = dStrdup(name);\n   }\n}\n\n/*\n * Save current URL\n */\nvoid a_UIcmd_save(void *vbw)\n{\n   BrowserWindow *bw = (BrowserWindow *)vbw;\n   const DilloUrl *url = a_History_get_url(NAV_TOP_UIDX(bw));\n\n   if (url) {\n      UIcmd_save(bw, url, \"Save Page as File\");\n   }\n}\n\n/*\n * Select a file\n */\nconst char *a_UIcmd_select_file()\n{\n   return a_Dialog_select_file(\"Dillo: Select a File\", NULL, NULL);\n}\n\n/*\n * Stop network activity on this bw.\n * The stop button was pressed: stop page (and images) downloads.\n */\nvoid a_UIcmd_stop(void *vbw)\n{\n   BrowserWindow *bw = (BrowserWindow *)vbw;\n\n   MSG(\"a_UIcmd_stop()\\n\");\n   a_Nav_cancel_expect(bw);\n   a_Bw_stop_clients(bw, BW_Root + BW_Img + BW_Force);\n   a_UIcmd_set_buttons_sens(bw);\n}\n\n/*\n * Popup the tools menu\n */\nvoid a_UIcmd_tools(void *vbw, int x, int y)\n{\n   a_Menu_tools_popup((BrowserWindow*)vbw, x, y);\n}\n\n/*\n * Open URL with dialog chooser\n */\nvoid a_UIcmd_open_file(void *vbw)\n{\n   char *name;\n   DilloUrl *url;\n\n   name = a_Dialog_open_file(\"Dillo: Open File\", NULL, \"\");\n\n   if (name) {\n      url = a_Url_new(name, \"file:\");\n      a_UIcmd_open_url((BrowserWindow*)vbw, url);\n      a_Url_free(url);\n      dFree(name);\n   }\n}\n\n/*\n * Returns a newly allocated string holding a search url generated from\n * a string of keywords (separated by blanks) and the current search_url.\n * The search string is urlencoded.\n */\nstatic char *UIcmd_make_search_str(const char *str)\n{\n   char *search_url, *l, *u, *c;\n   char *keys = a_Url_encode_hex_str(str),\n        *src = (char*)dList_nth_data(prefs.search_urls, prefs.search_url_idx);\n   Dstr *ds = dStr_sized_new(128);\n\n   /* parse search_url into label and url */\n   if (a_Misc_parse_search_url(src, &l, &u) == 0) {\n      for (c = u; *c; c++) {\n         if (*c == '%')\n            switch(*++c) {\n            case 's':\n               dStr_append(ds, keys); break;\n            case '%':\n               dStr_append_c(ds, '%'); break;\n            case 0:\n               MSG_WARN(\"search_url ends with '%%'\\n\"); c--; break;\n            default:\n               MSG_WARN(\"illegal specifier '%%%c' in search_url\\n\", *c);\n            }\n         else\n            dStr_append_c(ds, *c);\n      }\n   }\n   dFree(keys);\n\n   search_url = ds->str;\n   dStr_free(ds, 0);\n   return search_url;\n}\n\n/*\n * Get a query from a dialog and open it\n */\nvoid a_UIcmd_search_dialog(void *vbw)\n{\n   const char *query;\n\n   if ((query = a_Dialog_input(\"Dillo: Search\", \"Search the Web:\"))) {\n      char *url_str = UIcmd_make_search_str(query);\n      a_UIcmd_open_urlstr(vbw, url_str);\n      dFree(url_str);\n   }\n}\n\n/*\n * Get password for user\n */\nconst char *a_UIcmd_get_passwd(const char *user)\n{\n   const char *passwd;\n   const char *title = \"Dillo: Password\";\n   char *msg = dStrconcat(\"Password for user \\\"\", user, \"\\\"\", NULL);\n   passwd = a_Dialog_passwd(title, msg);\n   dFree(msg);\n   return passwd;\n}\n\n/*\n * Save link URL\n */\nvoid a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url)\n{\n   UIcmd_save(bw, url, \"Dillo: Save Link as File\");\n}\n\n/*\n * Request the bookmarks page\n */\nvoid a_UIcmd_book(void *vbw)\n{\n   DilloUrl *url = a_Url_new(\"dpi:/bm/\", NULL);\n   a_UIcmd_open_url((BrowserWindow*)vbw, url);\n   a_Url_free(url);\n}\n\n/*\n * Add a bookmark for a certain URL\n */\nvoid a_UIcmd_add_bookmark(BrowserWindow *bw, const DilloUrl *url)\n{\n   a_Bookmarks_add(bw, url);\n}\n\n\n/*\n * Popup the page menu\n */\nvoid a_UIcmd_page_popup(void *vbw, bool_t has_bugs, void *v_cssUrls)\n{\n   BrowserWindow *bw = (BrowserWindow*)vbw;\n   const DilloUrl *url = a_History_get_url(NAV_TOP_UIDX(bw));\n   a_Menu_page_popup(bw, url, has_bugs, v_cssUrls);\n}\n\n/*\n * Popup the link menu\n */\nvoid a_UIcmd_link_popup(void *vbw, const DilloUrl *url)\n{\n   a_Menu_link_popup((BrowserWindow*)vbw, url);\n}\n\n/*\n * Pop up the image menu\n */\nvoid a_UIcmd_image_popup(void *vbw, const DilloUrl *url, bool_t loaded_img,\n                         DilloUrl *page_url, DilloUrl *link_url)\n{\n   a_Menu_image_popup((BrowserWindow*)vbw, url, loaded_img, page_url,link_url);\n}\n\n/*\n * Pop up the form menu\n */\nvoid a_UIcmd_form_popup(void *vbw, const DilloUrl *url, void *vform,\n                        bool_t showing_hiddens)\n{\n   a_Menu_form_popup((BrowserWindow*)vbw, url, vform, showing_hiddens);\n}\n\n/*\n * Pop up the file menu\n */\nvoid a_UIcmd_file_popup(void *vbw, void *v_wid)\n{\n   a_Menu_file_popup((BrowserWindow*)vbw, v_wid);\n}\n\n/*\n * Copy url string to paste buffer\n */\nvoid a_UIcmd_copy_urlstr(BrowserWindow *bw, const char *urlstr)\n{\n   Layout *layout = (Layout*)bw->render_layout;\n   layout->copySelection(urlstr);\n}\n\n/*\n * Ask the vsource dpi to show this URL's source\n */\nvoid a_UIcmd_view_page_source(BrowserWindow *bw, const DilloUrl *url)\n{\n   char *buf, *major;\n   int buf_size;\n   Dstr *dstr_url;\n   DilloUrl *vs_url;\n   static int post_id = 0;\n   char tag[8];\n   const char *content_type = a_Nav_get_content_type(url);\n\n   a_Misc_parse_content_type(content_type, &major, NULL, NULL);\n\n   if (major && dStrAsciiCasecmp(major, \"image\") &&\n       a_Nav_get_buf(url, &buf, &buf_size)) {\n      a_Nav_set_vsource_url(url);\n      dstr_url = dStr_new(\"dpi:/vsource/:\");\n      dStr_append(dstr_url, URL_STR(url));\n      if (URL_FLAGS(url) & URL_Post) {\n         /* append a custom string to differentiate POST URLs */\n         post_id = (post_id < 9999) ? post_id + 1 : 0;\n         snprintf(tag, 8, \"_%.4d\", post_id);\n         dStr_append(dstr_url, tag);\n      }\n      vs_url = a_Url_new(dstr_url->str, NULL);\n      a_UIcmd_open_url_nt(bw, vs_url, 1);\n      a_Url_free(vs_url);\n      dStr_free(dstr_url, 1);\n      a_Nav_unref_buf(url);\n   }\n   dFree(major);\n}\n\n/*\n * Show the browser window's HTML errors in a text window\n */\nvoid a_UIcmd_view_page_bugs(void *vbw)\n{\n   BrowserWindow *bw = (BrowserWindow*)vbw;\n\n   if (bw->num_page_bugs > 0) {\n      a_Dialog_text_window(\"Dillo: Detected HTML errors\", bw->page_bugs->str);\n   } else {\n      a_Dialog_msg(\"Dillo: Good HTML!\", \"No HTML errors found while parsing!\");\n   }\n}\n\n/*\n * Popup the bug meter menu\n */\nvoid a_UIcmd_bugmeter_popup(void *vbw)\n{\n   BrowserWindow *bw = (BrowserWindow*)vbw;\n\n   a_Menu_bugmeter_popup(bw, a_History_get_url(NAV_TOP_UIDX(bw)));\n}\n\n/*\n * Make a list of URL indexes for the history popup\n * based on direction (-1 = back, 1 = forward)\n */\nint *a_UIcmd_get_history(BrowserWindow *bw, int direction)\n{\n   int i, j, n;\n   int *hlist;\n\n   // Count the number of URLs\n   i = a_Nav_stack_ptr(bw) + direction;\n   for (n = 0 ; i >= 0 && i < a_Nav_stack_size(bw); i+=direction)\n      ++n;\n   hlist = dNew(int, n + 1);\n\n   // Fill the list\n   i = a_Nav_stack_ptr(bw) + direction;\n   for (j = 0 ; i >= 0 && i < a_Nav_stack_size(bw); i+=direction, j += 1) {\n      hlist[j] = NAV_UIDX(bw,i);\n   }\n   hlist[j] = -1;\n\n   return hlist;\n}\n\n/*\n * Jump to a certain URL in the navigation stack.\n */\nvoid a_UIcmd_nav_jump(BrowserWindow *bw, int offset, int new_bw)\n{\n   a_Nav_jump(bw, offset, new_bw);\n}\n\n// UI binding functions -------------------------------------------------------\n\n/*\n * Return browser window width and height\n */\nvoid a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h)\n{\n   *w = BW2UI(bw)->w();\n   *h = BW2UI(bw)->h();\n   _MSG(\"a_UIcmd_wh: w=%d, h=%d\\n\", *w, *h);\n}\n\n/*\n * Get the scroll position (x, y offset pair)\n */\nvoid a_UIcmd_get_scroll_xy(BrowserWindow *bw, int *x, int *y)\n{\n   Layout *layout = (Layout*)bw->render_layout;\n\n   if (layout) {\n     *x = layout->getScrollPosX();\n     *y = layout->getScrollPosY();\n   }\n}\n\n/*\n * Set the scroll position ({x, y} offset pair)\n */\nvoid a_UIcmd_set_scroll_xy(BrowserWindow *bw, int x, int y)\n{\n   Layout *layout = (Layout*)bw->render_layout;\n\n   if (layout) {\n      layout->scrollTo(HPOS_LEFT, VPOS_TOP, x, y, 0, 0);\n   }\n}\n\n/*\n * Set the scroll position by fragment (from URL)\n */\nvoid a_UIcmd_set_scroll_by_fragment(BrowserWindow *bw, const char *f)\n{\n   Layout *layout = (Layout*)bw->render_layout;\n\n   if (layout && f) {\n      layout->setAnchor(f);\n   }\n}\n\n/*\n * Pass scrolling command to dw.\n */\nvoid a_UIcmd_scroll(BrowserWindow *bw, int icmd)\n{\n   Layout *layout = (Layout*)bw->render_layout;\n\n   if (layout) {\n      typedef struct {\n         KeysCommand_t keys_cmd;\n         ScrollCommand dw_cmd;\n      } mapping_t;\n\n      const mapping_t map[] = {\n         {KEYS_SCREEN_UP, SCREEN_UP_CMD},\n         {KEYS_SCREEN_DOWN, SCREEN_DOWN_CMD},\n         {KEYS_SCREEN_LEFT, SCREEN_LEFT_CMD},\n         {KEYS_SCREEN_RIGHT, SCREEN_RIGHT_CMD},\n         {KEYS_LINE_UP, LINE_UP_CMD},\n         {KEYS_LINE_DOWN, LINE_DOWN_CMD},\n         {KEYS_LEFT, LEFT_CMD},\n         {KEYS_RIGHT, RIGHT_CMD},\n         {KEYS_TOP, TOP_CMD},\n         {KEYS_BOTTOM, BOTTOM_CMD},\n      };\n      KeysCommand_t keycmd = (KeysCommand_t)icmd;\n\n      for (uint_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) {\n         if (keycmd == map[i].keys_cmd) {\n            layout->scroll(map[i].dw_cmd);\n            break;\n         }\n      }\n   }\n}\n\n/*\n * Get location's text\n */\nchar *a_UIcmd_get_location_text(BrowserWindow *bw)\n{\n   return dStrdup(BW2UI(bw)->get_location());\n}\n\n/*\n * Set location's text\n */\nvoid a_UIcmd_set_location_text(void *vbw, const char *text)\n{\n   BrowserWindow *bw = (BrowserWindow*)vbw;\n   BW2UI(bw)->set_location(text);\n}\n\n/*\n * Set the page progress bar\n * cmd: 0 Deactivate, 1 Update, 2 Clear\n */\nvoid a_UIcmd_set_page_prog(BrowserWindow *bw, size_t nbytes, int cmd)\n{\n   BW2UI(bw)->set_page_prog(nbytes, cmd);\n}\n\n/*\n * Set the images progress bar\n * cmd: 0 Deactivate, 1 Update, 2 Clear\n */\nvoid a_UIcmd_set_img_prog(BrowserWindow *bw, int n_img, int t_img, int cmd)\n{\n   BW2UI(bw)->set_img_prog(n_img, t_img, cmd);\n#if 0\n   if (!cmd)\n      a_UIcmd_close_bw(bw);\n#endif\n}\n\n/*\n * Set the bug meter progress label\n */\nvoid a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug)\n{\n   BW2UI(bw)->set_bug_prog(n_bug);\n}\n\n/*\n * Set the page title in the tab label and window titlebar.\n * (Update window titlebar for the current tab only)\n */\nvoid a_UIcmd_set_page_title(BrowserWindow *bw, const char *label)\n{\n   const int size = 128;\n   char title[size];\n\n   if (snprintf(title, size, \"Dillo: %s\", label ? label : \"\") >= size) {\n      uint_t i = MIN(size - 4, 1 + a_Utf8_end_of_char(title, size - 8));\n      snprintf(title + i, 4, \"...\");\n   }\n   BW2UI(bw)->copy_label(title);\n   BW2UI(bw)->tabs()->set_tab_label(BW2UI(bw), label ? label : \"\");\n\n   if (a_UIcmd_get_bw_by_widget(BW2UI(bw)->tabs()->wizard()->value()) == bw) {\n      // This is the focused bw, set window title\n      UIcmd_set_window_labels(BW2UI(bw)->window(), title);\n   }\n}\n\n/*\n * Set a printf-like status string on the bottom of the dillo window.\n * Beware: The safe way to set an arbitrary string is\n *         a_UIcmd_set_msg(bw, \"%s\", str)\n */\nvoid a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...)\n{\n   va_list argp;\n   Dstr *ds = dStr_sized_new(128);\n   va_start(argp, format);\n   dStr_vsprintf(ds, format, argp);\n   va_end(argp);\n   BW2UI(bw)->set_status(ds->str);\n   dStr_free(ds, 1);\n}\n\n/*\n * Set the sensitivity of back/forw/stop buttons.\n */\nvoid a_UIcmd_set_buttons_sens(BrowserWindow *bw)\n{\n   int sens;\n\n   // Stop\n   sens = (dList_length(bw->ImageClients) || dList_length(bw->RootClients));\n   BW2UI(bw)->button_set_sens(UI_STOP, sens);\n   // Back\n   sens = (a_Nav_stack_ptr(bw) > 0);\n   BW2UI(bw)->button_set_sens(UI_BACK, sens);\n   // Forward\n   sens = (a_Nav_stack_ptr(bw) < a_Nav_stack_size(bw) - 1 &&\n           !a_Bw_expecting(bw));\n   BW2UI(bw)->button_set_sens(UI_FORW, sens);\n}\n\n/*\n * Toggle control panel\n */\nvoid a_UIcmd_panels_toggle(BrowserWindow *bw)\n{\n   BW2UI(bw)->panels_toggle();\n}\n\n/*\n * Search for next/previous occurrence of key.\n */\nvoid a_UIcmd_findtext_search(BrowserWindow *bw, const char *key,\n                             int case_sens, int backward)\n{\n   Layout *l = (Layout *)bw->render_layout;\n\n   switch (l->search(key, case_sens, backward)) {\n   case FindtextState::RESTART:\n      a_UIcmd_set_msg(bw, backward?\"Top reached; restarting from the bottom.\"\n                                  :\"Bottom reached; restarting from the top.\");\n      break;\n   case FindtextState::NOT_FOUND:\n      a_UIcmd_set_msg(bw, \"\\\"%s\\\" not found.\", key);\n      break;\n   case FindtextState::SUCCESS:\n   default:\n      a_UIcmd_set_msg(bw, \"\");\n   }\n}\n\n/*\n * Reset text search state.\n */\nvoid a_UIcmd_findtext_reset(BrowserWindow *bw)\n{\n   Layout *l = (Layout *)bw->render_layout;\n   l->resetSearch();\n\n   a_UIcmd_set_msg(bw, \"\");\n}\n\n/*\n * Tell the UI to hide/show the findbar\n */\nvoid a_UIcmd_findbar_toggle(BrowserWindow *bw, int on)\n{\n   BW2UI(bw)->findbar_toggle(on);\n}\n\n/*\n * Focus the rendered area.\n */\nvoid a_UIcmd_focus_main_area(BrowserWindow *bw)\n{\n   BW2UI(bw)->focus_main();\n}\n\n/*\n * Focus the location bar.\n */\nvoid a_UIcmd_focus_location(void *vbw)\n{\n   BrowserWindow *bw = (BrowserWindow*)vbw;\n   BW2UI(bw)->focus_location();\n}\n\n"
  },
  {
    "path": "src/uicmd.hh",
    "content": "#ifndef __UICMD_HH__\n#define __UICMD_HH__\n\n#include \"bw.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\nBrowserWindow *a_UIcmd_browser_window_new(int ww, int wh,\n                                          uint32_t xid, const void *v_bw);\nBrowserWindow *a_UIcmd_get_bw_by_widget(void *v_wid);\nvoid a_UIcmd_send_event_to_tabs_by_wid(int e, void *v_wid);\nvoid a_UIcmd_open_urlstr(void *vbw, const char *urlstr);\nvoid a_UIcmd_open_url(BrowserWindow *bw, const DilloUrl *url);\nvoid a_UIcmd_open_url_nw(BrowserWindow *bw, const DilloUrl *url);\nvoid a_UIcmd_open_url_nt(void *vbw, const DilloUrl *url, int focus);\nvoid a_UIcmd_back(void *vbw);\nvoid a_UIcmd_back_popup(void *vbw, int x, int y);\nvoid a_UIcmd_forw(void *vbw);\nvoid a_UIcmd_forw_popup(void *vbw, int x, int y);\nvoid a_UIcmd_home(void *vbw);\nvoid a_UIcmd_reload(void *vbw);\nvoid a_UIcmd_repush(void *vbw);\nvoid a_UIcmd_redirection0(void *vbw, const DilloUrl *url);\nvoid a_UIcmd_save(void *vbw);\nvoid a_UIcmd_stop(void *vbw);\nvoid a_UIcmd_tools(void *vbw, int x, int y);\nvoid a_UIcmd_save_link(BrowserWindow *bw, const DilloUrl *url);\nvoid a_UIcmd_open_file(void *vbw);\nconst char *a_UIcmd_select_file();\nvoid a_UIcmd_search_dialog(void *vbw);\nconst char *a_UIcmd_get_passwd(const char *user);\nvoid a_UIcmd_book(void *vbw);\nvoid a_UIcmd_add_bookmark(BrowserWindow *bw, const DilloUrl *url);\nvoid a_UIcmd_panels_toggle(BrowserWindow *bw);\nvoid a_UIcmd_findtext_dialog(BrowserWindow *bw);\nvoid a_UIcmd_findtext_search(BrowserWindow *bw,const char *key,int case_sens,\n                             int backwards);\nvoid a_UIcmd_findtext_reset(BrowserWindow *bw);\nvoid a_UIcmd_findbar_toggle(BrowserWindow *bw, int on);\nvoid a_UIcmd_focus_main_area(BrowserWindow *bw);\nvoid a_UIcmd_focus_location(void *vbw);\nvoid a_UIcmd_page_popup(void *vbw, bool_t has_bugs, void *v_cssUrls);\nvoid a_UIcmd_link_popup(void *vbw, const DilloUrl *url);\nvoid a_UIcmd_image_popup(void *vbw, const DilloUrl *url, bool_t loaded_img,\n                         DilloUrl *page_url, DilloUrl *link_url);\nvoid a_UIcmd_form_popup(void *vbw, const DilloUrl *url, void *vform,\n                        bool_t showing_hiddens);\nvoid a_UIcmd_file_popup(void *vbw, void *v_wid);\nvoid a_UIcmd_copy_urlstr(BrowserWindow *bw, const char *urlstr);\nvoid a_UIcmd_view_page_source(BrowserWindow *bw, const DilloUrl *url);\nvoid a_UIcmd_view_page_bugs(void *vbw);\nvoid a_UIcmd_bugmeter_popup(void *vbw);\nint *a_UIcmd_get_history(BrowserWindow *bw, int direction);\nvoid a_UIcmd_nav_jump(BrowserWindow *bw, int offset, int new_bw);\n\nvoid a_UIcmd_close_bw(void *vbw);\nvoid a_UIcmd_close_all_bw(void *p);\n\nvoid a_UIcmd_init(void);\n\n\n// UI binding functions -------------------------------------------------------\n\nvoid a_UIcmd_get_wh(BrowserWindow *bw, int *w, int *h);\nvoid a_UIcmd_get_scroll_xy(BrowserWindow *bw, int *x, int *y);\nvoid a_UIcmd_set_scroll_xy(BrowserWindow *bw, int x, int y);\nvoid a_UIcmd_set_scroll_by_fragment(BrowserWindow *bw, const char *f);\nvoid a_UIcmd_scroll(BrowserWindow *bw, int icmd);\nchar *a_UIcmd_get_location_text(BrowserWindow *bw);\nvoid a_UIcmd_set_location_text(void *vbw, const char *text);\nvoid a_UIcmd_set_page_prog(BrowserWindow *bw, size_t nbytes, int cmd);\nvoid a_UIcmd_set_img_prog(BrowserWindow *bw, int n_img, int t_img, int cmd);\nvoid a_UIcmd_set_bug_prog(BrowserWindow *bw, int n_bug);\nvoid a_UIcmd_set_page_title(BrowserWindow *bw, const char *label);\nvoid a_UIcmd_set_msg(BrowserWindow *bw, const char *format, ...);\nvoid a_UIcmd_set_buttons_sens(BrowserWindow *bw);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif // __UICMD_HH__\n"
  },
  {
    "path": "src/url.c",
    "content": "/*\n * File: url.c\n *\n * Copyright (C) 2001-2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n/*\n * Parse and normalize all URL's inside Dillo.\n *  - <scheme> <authority> <path> <query> and <fragment> point to 'buffer'.\n *  - 'url_string' is built upon demand (transparent to the caller).\n *  - 'hostname' and 'port' are also being handled on demand.\n */\n\n/*\n * Regular Expression as given in RFC3986 for URL parsing.\n *\n *  ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?\n *   12            3  4          5       6  7        8 9\n *\n *  scheme    = $2\n *  authority = $4\n *  path      = $5\n *  query     = $7\n *  fragment  = $9\n *\n *\n *  RFC-2396 BNF:\n *\n *  absoluteURI = scheme \":\" (hier_part | opaque_part)\n *  hier_part   = (net_path | abs_path) [\"?\" query]\n *  net_path    = \"//\" authority[abs_path]\n *  abs_path    = \"/\" path_segments\n *\n *  Notes:\n *    - \"undefined\" means \"preceeding separator does not appear\".\n *    - path is never \"undefined\" though it may be \"empty\".\n */\n\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n\n#include \"url.h\"\n#include \"hsts.h\"\n#include \"msg.h\"\n\nstatic const char *HEX = \"0123456789ABCDEF\";\n\n/* URL-field compare methods */\n#define URL_STR_FIELD_CMP(s1,s2) \\\n   (s1) && (s2) ? strcmp(s1,s2) : !(s1) && !(s2) ? 0 : (s1) ? 1 : -1\n#define URL_STR_FIELD_I_CMP(s1,s2) \\\n   (s1) && (s2) ? dStrAsciiCasecmp(s1,s2) : !(s1) && !(s2) ? 0 : (s1) ? 1 : -1\n\n/*\n * Return the url as a string.\n * (initializing 'url_string' field if necessary)\n */\nchar *a_Url_str(const DilloUrl *u)\n{\n   /* Internal url handling IS transparent to the caller */\n   DilloUrl *url = (DilloUrl *) u;\n\n   dReturn_val_if_fail (url != NULL, NULL);\n\n   url->path = (! url->path)      ? \"/\" : url->path;\t// Make sure that the path is at least \"/\"\n\n   if (!url->url_string) {\n      url->url_string = dStr_sized_new(60);\n      dStr_sprintf(\n         url->url_string, \"%s%s%s%s%s%s%s%s%s%s\",\n         url->scheme    ? url->scheme : \"\",\n         url->scheme    ? \":\" : \"\",\n         url->authority ? \"//\" : \"\",\n         url->authority ? url->authority : \"\",\n         // (url->path && url->path[0] != '/' && url->authority) ? \"/\" : \"\",\n         (url->authority && (!url->path || *url->path != '/')) ? \"/\" : \"\",\n         url->path      ? url->path : \"\",\n         url->query     ? \"?\" : \"\",\n         url->query     ? url->query : \"\",\n         url->fragment  ? \"#\" : \"\",\n         url->fragment  ? url->fragment : \"\");\n   }\n\n   return url->url_string->str;\n}\n\n/*\n * Return the hostname as a string.\n * (initializing 'hostname' and 'port' fields if necessary)\n * Note: a similar approach can be taken for user:password auth.\n */\nconst char *a_Url_hostname(const DilloUrl *u)\n{\n   char *p;\n   /* Internal url handling IS transparent to the caller */\n   DilloUrl *url = (DilloUrl *) u;\n\n   if (!url->hostname && url->authority) {\n      if (url->authority[0] == '[' && (p = strchr(url->authority, ']'))) {\n         /* numeric ipv6 address, strip the brackets */\n         url->hostname = dStrndup(url->authority + 1,\n                                  (uint_t)(p - url->authority - 1));\n         if ((p = strchr(p, ':'))) {\n            url->port = strtol(p + 1, NULL, 10);\n         }\n      } else {\n         /* numeric ipv4 or hostname */\n         if ((p = strchr(url->authority, ':'))) {\n            url->port = strtol(p + 1, NULL, 10);\n            url->hostname = dStrndup(url->authority,\n                                     (uint_t)(p - url->authority));\n         } else {\n            url->hostname = url->authority;\n         }\n      }\n   }\n\n   if (!url->port) {\n      if (!dStrAsciiCasecmp(url->scheme, \"http\"))\n         url->port = URL_HTTP_PORT;\n      else if (!dStrAsciiCasecmp(url->scheme, \"https\"))\n         url->port = URL_HTTPS_PORT;\n   }\n   return url->hostname;\n}\n\n/*\n *  Create a DilloUrl object and initialize it.\n *  (buffer, scheme, authority, path, query and fragment).\n */\nstatic DilloUrl *Url_object_new(const char *uri_str)\n{\n   DilloUrl *url;\n   char *s, *p;\n\n   dReturn_val_if_fail (uri_str != NULL, NULL);\n\n   url = dNew0(DilloUrl, 1);\n\n   /* url->buffer is given a little extra room in case HSTS needs to transform\n    * a URL string ending in \":80\" to \":443\".\n    */\n   int len = strlen(uri_str)+2;\n   s = dNew(char, len);\n   memcpy(s, uri_str, len-1);\n   s = dStrstrip(s);\n\n   /* remove leading & trailing space from buffer */\n   url->buffer = s;\n\n   p = strpbrk(s, \":/?#\");\n   if (p && p[0] == ':' && p > s) {                /* scheme */\n      *p = 0;\n      url->scheme = s;\n      s = ++p;\n   }\n   /* p = strpbrk(s, \"/\"); */\n   if (p == s && p[0] == '/' && p[1] == '/') {     /* authority */\n      s = p + 2;\n      p = strpbrk(s, \"/?#\");\n      if (p) {\n         memmove(s - 2, s, (size_t)MAX(p - s, 1));\n         url->authority = s - 2;\n         p[-2] = 0;\n         s = p;\n      } else if (*s) {\n         url->authority = s;\n         return url;\n      }\n   }\n\n   p = strpbrk(s, \"?#\");\n   if (p) {                                        /* path */\n      url->path = (p > s) ? s : NULL;\n      s = p;\n   } else if (*s) {\n      url->path = s;\n      return url;\n   }\n\n   p = strpbrk(s, \"?#\");\n   if (p && p[0] == '?') {                         /* query */\n      *p = 0;\n      s = p + 1;\n      url->query = s;\n      p = strpbrk(s, \"#\");\n      url->flags |= URL_Get;\n   }\n   if (p && p[0] == '#') {                         /* fragment */\n      *p = 0;\n      s = p + 1;\n      url->fragment = s;\n   }\n\n   return url;\n}\n\n/*\n *  Free a DilloUrl\n *  Do nothing if the argument is NULL\n */\nvoid a_Url_free(DilloUrl *url)\n{\n   if (url) {\n      if (url->url_string)\n         dStr_free(url->url_string, TRUE);\n      if (url->hostname != url->authority)\n         dFree((char *)url->hostname);\n      dFree((char *)url->buffer);\n      dStr_free(url->data, 1);\n      dFree(url);\n   }\n}\n\n/*\n * Resolve the URL as RFC3986 suggests.\n */\nstatic Dstr *Url_resolve_relative(const char *RelStr,\n                                  const char *BaseStr)\n{\n   char *p, *s, *e;\n   int i;\n   Dstr *SolvedUrl, *Path;\n   DilloUrl *RelUrl, *BaseUrl = NULL;\n\n   /* parse relative URL */\n   RelUrl = Url_object_new(RelStr);\n\n   if (RelUrl->scheme == NULL) {\n      /* only required when there's no <scheme> in RelStr */\n      BaseUrl = Url_object_new(BaseStr);\n   }\n\n   SolvedUrl = dStr_sized_new(64);\n   Path = dStr_sized_new(64);\n\n   /* path empty && scheme and authority undefined */\n   if (!RelUrl->path && !RelUrl->scheme && !RelUrl->authority) {\n      dStr_append(SolvedUrl, BaseStr);\n      if ((p = strchr(SolvedUrl->str, '#')))\n         dStr_truncate(SolvedUrl, p - SolvedUrl->str);\n      if (!BaseUrl->path)\n         dStr_append_c(SolvedUrl, '/');\n\n      if (RelUrl->query) {                        /* query */\n         if (BaseUrl->query)\n            dStr_truncate(SolvedUrl, BaseUrl->query - BaseUrl->buffer - 1);\n         dStr_append_c(SolvedUrl, '?');\n         dStr_append(SolvedUrl, RelUrl->query);\n      }\n      if (RelUrl->fragment) {                    /* fragment */\n         dStr_append_c(SolvedUrl, '#');\n         dStr_append(SolvedUrl, RelUrl->fragment);\n      }\n      goto done;\n\n   } else if (RelUrl->scheme) {                  /* scheme */\n      dStr_append(SolvedUrl, RelStr);\n      goto done;\n\n   } else if (RelUrl->authority) {               /* authority */\n      // Set the Path buffer and goto \"STEP 7\";\n      if (RelUrl->path)\n         dStr_append(Path, RelUrl->path);\n\n   } else {\n      if (RelUrl->path && RelUrl->path[0] == '/') {   /* absolute path */\n         ; /* Ignore BaseUrl path */\n      } else if (BaseUrl->path) {                     /* relative path */\n         dStr_append(Path, BaseUrl->path);\n         for (i = Path->len; --i >= 0 && Path->str[i] != '/'; ) ;\n         if (i >= 0 && Path->str[i] == '/')\n            dStr_truncate(Path, ++i);\n      }\n      if (RelUrl->path)\n         dStr_append(Path, RelUrl->path);\n\n      // erase \"./\"\n      while ((p=strstr(Path->str, \"./\")) &&\n             (p == Path->str || p[-1] == '/'))\n         dStr_erase(Path, p - Path->str, 2);\n      // erase last \".\"\n      if (Path->len && Path->str[Path->len - 1] == '.' &&\n          (Path->len == 1 || Path->str[Path->len - 2] == '/'))\n         dStr_truncate(Path, Path->len - 1);\n\n      // erase \"<segment>/../\" and \"<segment>/..\"\n      s = p = Path->str;\n      while ( (p = strstr(p, \"/..\")) != NULL ) {\n         if (p[3] == '/' || !p[3]) { //  \"/../\" | \"/..\"\n            for (e = p + 3 ; p > s && p[-1] != '/'; --p) ;\n            dStr_erase(Path, p - Path->str, e - p + (p > s && *e != 0));\n            p -= (p > Path->str);\n         } else\n            p += 3;\n      }\n   }\n\n   /* STEP 7\n    */\n\n   /* scheme */\n   if (BaseUrl->scheme) {\n      dStr_append(SolvedUrl, BaseUrl->scheme);\n      dStr_append_c(SolvedUrl, ':');\n   }\n\n   /* authority */\n   if (RelUrl->authority) {\n      dStr_append(SolvedUrl, \"//\");\n      dStr_append(SolvedUrl, RelUrl->authority);\n   } else if (BaseUrl->authority) {\n      dStr_append(SolvedUrl, \"//\");\n      dStr_append(SolvedUrl, BaseUrl->authority);\n   }\n\n   /* path */\n   if ((RelUrl->authority || BaseUrl->authority) &&\n       ((Path->len == 0 && (RelUrl->query || RelUrl->fragment)) ||\n        (Path->len && Path->str[0] != '/')))\n      dStr_append_c(SolvedUrl, '/'); /* hack? */\n   dStr_append(SolvedUrl, Path->str);\n\n   /* query */\n   if (RelUrl->query) {\n      dStr_append_c(SolvedUrl, '?');\n      dStr_append(SolvedUrl, RelUrl->query);\n   }\n\n   /* fragment */\n   if (RelUrl->fragment) {\n      dStr_append_c(SolvedUrl, '#');\n      dStr_append(SolvedUrl, RelUrl->fragment);\n   }\n\ndone:\n   dStr_free(Path, TRUE);\n   a_Url_free(RelUrl);\n   a_Url_free(BaseUrl);\n   return SolvedUrl;\n}\n\n/*\n *  Transform (and resolve) an URL string into the respective DilloURL.\n *  If URL  =  \"http://dillo.sf.net:8080/index.html?long#part2\"\n *  then the resulting DilloURL should be:\n *  DilloURL = {\n *     url_string         = \"http://dillo.sf.net:8080/index.html?long#part2\"\n *     scheme             = \"http\"\n *     authority          = \"dillo.sf.net:8080:\n *     path               = \"/index.html\"\n *     query              = \"long\"\n *     fragment           = \"part2\"\n *     hostname           = \"dillo.sf.net\"\n *     port               = 8080\n *     flags              = URL_Get\n *     data               = Dstr * (\"\")\n *     ismap_url_len      = 0\n *  }\n *\n *  Return NULL if URL is badly formed.\n */\nDilloUrl* a_Url_new(const char *url_str, const char *base_url)\n{\n   DilloUrl *url;\n   char *urlstr = (char *)url_str;  /* auxiliar variable, don't free */\n   char *p, *str1 = NULL, *str2 = NULL;\n   Dstr *SolvedUrl;\n   int i, n_ic, n_ic_spc;\n\n   dReturn_val_if_fail (url_str != NULL, NULL);\n\n   /* Count illegal characters (0x00-0x1F, 0x7F-0xFF and space) */\n   n_ic = n_ic_spc = 0;\n   for (p = (char*)url_str; *p; p++) {\n      n_ic_spc += (*p == ' ') ? 1 : 0;\n      n_ic += (*p != ' ' && *p > 0x1F && *p < 0x7F) ? 0 : 1;\n   }\n   if (n_ic) {\n      /* Encode illegal characters (they could also be stripped).\n       * There's no standard for illegal chars; we chose to encode. */\n      p = str1 = dNew(char, strlen(url_str) + 2*n_ic + 1);\n      for (i = 0; url_str[i]; ++i)\n         if (url_str[i] > 0x1F && url_str[i] < 0x7F && url_str[i] != ' ')\n            *p++ = url_str[i];\n         else {\n            *p++ = '%';\n            *p++ = HEX[(url_str[i] >> 4) & 15];\n            *p++ = HEX[url_str[i] & 15];\n         }\n      *p = 0;\n      urlstr = str1;\n   }\n\n   /* let's use a heuristic to set http: as default */\n   if (!base_url) {\n      base_url = \"http:\";\n      if (urlstr[0] != '/') {\n         p = strpbrk(urlstr, \"/#?:\");\n         if (!p || *p != ':')\n            urlstr = str2 = dStrconcat(\"//\", urlstr, NULL);\n      } else if (urlstr[1] != '/')\n         urlstr = str2 = dStrconcat(\"/\", urlstr, NULL);\n   }\n\n   /* Resolve the URL */\n   SolvedUrl = Url_resolve_relative(urlstr, base_url);\n   _MSG(\"SolvedUrl = %s\\n\", SolvedUrl->str);\n\n   /* Fill url data */\n   url = Url_object_new(SolvedUrl->str);\n   url->data = dStr_new(\"\");\n   url->url_string = SolvedUrl;\n   url->illegal_chars = n_ic;\n   url->illegal_chars_spc = n_ic_spc;\n\n   dFree(str1);\n   dFree(str2);\n\n   /*\n    * A site's HTTP Strict Transport Security policy may direct us to transform\n    * URLs like \"http://en.wikipedia.org:80\" to \"https://en.wikipedia.org:443\".\n    */\n   if (prefs.http_strict_transport_security &&\n       url->scheme && !dStrAsciiCasecmp(url->scheme, \"http\") &&\n       a_Hsts_require_https(a_Url_hostname(url))) {\n      const char *const scheme = \"https\";\n\n      _MSG(\"url: HSTS transformation for %s.\\n\", url->url_string->str);\n      url->scheme = scheme;\n      if (url->port == URL_HTTP_PORT)\n         url->port = URL_HTTPS_PORT;\n\n      if (url->authority) {\n         int len = strlen(url->authority);\n\n         if (len >= 3 && !strcmp(url->authority + len-3, \":80\")) {\n            strcpy((char *)url->authority + len-2, \"443\");\n         }\n      }\n\n      dStr_free(url->url_string, TRUE);\n      url->url_string = NULL;\n   }\n\n   return url;\n}\n\n\n/*\n *  Duplicate a Url structure\n */\nDilloUrl* a_Url_dup(const DilloUrl *ori)\n{\n   DilloUrl *url;\n\n   url = Url_object_new(URL_STR_(ori));\n   dReturn_val_if_fail (url != NULL, NULL);\n\n   url->url_string           = dStr_new(URL_STR(ori));\n   url->port                 = ori->port;\n   url->flags                = ori->flags;\n   url->ismap_url_len        = ori->ismap_url_len;\n   url->illegal_chars        = ori->illegal_chars;\n   url->illegal_chars_spc    = ori->illegal_chars_spc;\n   url->data                 = dStr_sized_new(URL_DATA(ori)->len);\n   dStr_append_l(url->data, URL_DATA(ori)->str, URL_DATA(ori)->len);\n   return url;\n}\n\n/*\n *  Compare two Url's to check if they're the same, or which one is bigger.\n *\n *  The fields which are compared here are:\n *  <scheme>, <authority>, <path>, <query> and <data>\n *  Other fields are left for the caller to check\n *\n *  Return value: 0 if equal, > 0 if A > B, < 0 if A < B.\n *\n *  Note: this function defines a sorting order different from strcmp!\n */\nint a_Url_cmp(const DilloUrl *A, const DilloUrl *B)\n{\n   int st;\n\n   dReturn_val_if_fail(A && B, 1);\n\n   if (A == B ||\n       ((st = URL_STR_FIELD_I_CMP(A->authority, B->authority)) == 0 &&\n        (st = strcmp(A->path ? A->path + (*A->path == '/') : \"\",\n                     B->path ? B->path + (*B->path == '/') : \"\")) == 0 &&\n        //(st = URL_STR_FIELD_CMP(A->path, B->path)) == 0 &&\n        (st = URL_STR_FIELD_CMP(A->query, B->query)) == 0 &&\n        (st = dStr_cmp(A->data, B->data)) == 0 &&\n        (st = URL_STR_FIELD_I_CMP(A->scheme, B->scheme)) == 0))\n      return 0;\n   return st;\n}\n\n/*\n * Set DilloUrl flags\n */\nvoid a_Url_set_flags(DilloUrl *u, int flags)\n{\n   if (u)\n      u->flags = flags;\n}\n\n/*\n * Set DilloUrl data (like POST info, etc.)\n */\nvoid a_Url_set_data(DilloUrl *u, Dstr **data)\n{\n   if (u) {\n      dStr_free(u->data, 1);\n      u->data = *data;\n      *data = NULL;\n   }\n}\n\n/*\n * Set DilloUrl ismap coordinates\n * (this is optimized for not hogging the CPU)\n */\nvoid a_Url_set_ismap_coords(DilloUrl *u, char *coord_str)\n{\n   dReturn_if_fail (u && coord_str);\n\n   if (!u->ismap_url_len) {\n      /* Save base-url length (without coords) */\n      u->ismap_url_len = URL_STR_(u) ? u->url_string->len : 0;\n   }\n   if (u->url_string) {\n      dStr_truncate(u->url_string, u->ismap_url_len);\n      dStr_append(u->url_string, coord_str);\n      u->query = u->url_string->str + u->ismap_url_len + 1;\n   }\n}\n\n/*\n * Given an hex octet (e.g., e3, 2F, 20), return the corresponding\n * character if the octet is valid, and -1 otherwise\n */\nstatic int Url_decode_hex_octet(const char *s)\n{\n   int hex_value;\n   char *tail, hex[3];\n\n   if (s && (hex[0] = s[0]) && (hex[1] = s[1])) {\n      hex[2] = 0;\n      hex_value = strtol(hex, &tail, 16);\n      if (tail - hex == 2)\n        return hex_value;\n   }\n   return -1;\n}\n\n/*\n * Parse possible hexadecimal octets in the URI path.\n * Returns a new allocated string.\n */\nchar *a_Url_decode_hex_str(const char *str)\n{\n   char *new_str, *dest;\n   int i, val;\n\n   if (!str)\n      return NULL;\n\n   /* most cases won't have hex octets */\n   if (!strchr(str, '%'))\n      return dStrdup(str);\n\n   dest = new_str = dNew(char, strlen(str) + 1);\n\n   for (i = 0; str[i]; i++) {\n      *dest++ = (str[i] == '%' && (val = Url_decode_hex_octet(str+i+1)) >= 0) ?\n                i+=2, val : str[i];\n   }\n   *dest++ = 0;\n\n   new_str = dRealloc(new_str, sizeof(char) * (dest - new_str));\n   return new_str;\n}\n\n/*\n * Urlencode 'str'\n * -RL :: According to the RFC 1738, only alphanumerics, the special\n *        characters \"$-_.+!*'(),\", and reserved characters \";/?:@=&\" used\n *        for their *reserved purposes* may be used unencoded within a URL.\n * We'll escape everything but alphanumeric and \"-_.*\" (as lynx).  --Jcid\n *\n * Note: the content type \"application/x-www-form-urlencoded\" is used:\n *       i.e., ' ' -> '+' and '\\n' -> CR LF (see HTML 4.01, Sec. 17.13.4)\n */\nchar *a_Url_encode_hex_str(const char *str)\n{\n   static const char *const verbatim = \"-_.*\";\n   char *newstr, *c;\n\n   if (!str)\n      return NULL;\n\n   newstr = dNew(char, 6*strlen(str)+1);\n\n   for (c = newstr; *str; str++)\n      if ((dIsalnum(*str) && isascii(*str)) || strchr(verbatim, *str))\n         *c++ = *str;\n      else if (*str == ' ')\n         *c++ = '+';\n      else if (*str == '\\n') {\n         *c++ = '%';\n         *c++ = '0';\n         *c++ = 'D';\n         *c++ = '%';\n         *c++ = '0';\n         *c++ = 'A';\n      } else {\n         *c++ = '%';\n         *c++ = HEX[(*str >> 4) & 15];\n         *c++ = HEX[*str & 15];\n      }\n   *c = 0;\n\n  return newstr;\n}\n\n\n/*\n * RFC-3986 suggests this stripping when \"importing\" URLs from other media.\n * Strip: \"URL:\", enclosing < >, and embedded whitespace.\n * (We also strip illegal chars: 00-1F and 7F-FF)\n */\nchar *a_Url_string_strip_delimiters(const char *str)\n{\n   char *p, *new_str, *text;\n\n   new_str = text = dStrdup(str);\n\n   if (new_str) {\n      if (strncmp(new_str, \"URL:\", 4) == 0)\n         text += 4;\n      if (*text == '<')\n         text++;\n\n      for (p = new_str; *text; text++)\n         if (*text > 0x1F && *text < 0x7F && *text != ' ')\n            *p++ = *text;\n      if (p > new_str && p[-1] == '>')\n         --p;\n      *p = 0;\n   }\n   return new_str;\n}\n\n/*\n * What type of host is this?\n */\nint a_Url_host_type(const char *host)\n{\n   uint_t len;\n\n   if (!host || !*host)\n      return URL_HOST_ERROR;\n\n   len = strlen(host);\n\n   if (len == strspn(host, \"0123456789.\")) {\n      return URL_HOST_IPV4;\n   }\n   if (strchr(host, ':') &&\n       (len == strspn(host, \"0123456789abcdefABCDEF:.\"))) {\n      /* The precise format is shown in section 3.2.2 of rfc 3986 */\n      return URL_HOST_IPV6;\n   }\n   return URL_HOST_NAME;\n}\n\n/*\n * How many internal dots are in the public portion of this hostname?\n * e.g., for \"www.dillo.org\", it is one because everything under \"dillo.org\",\n * as a .org domain, is part of one organization.\n *\n * Of course this is only a simple and imperfect approximation of\n * organizational boundaries.\n */\nstatic uint_t Url_host_public_internal_dots(const char *host)\n{\n   uint_t ret = 1;\n\n   if (host) {\n      int start, after, tld_len;\n\n      /* We may be able to trust the format of the host string more than\n       * I am here. Trailing dots and no dots are real possibilities, though.\n       */\n      after = strlen(host);\n      if (after > 0 && host[after - 1] == '.')\n         after--;\n      start = after;\n      while (start > 0 && host[start - 1] != '.')\n         start--;\n      tld_len = after - start;\n\n      if (tld_len > 0) {\n         /* These TLDs were chosen by examining the current publicsuffix list\n          * in July 2016 and picking out those where it was simplest for\n          * them to describe the situation by beginning with a \"*.[tld]\" rule\n          * or every rule was \"[something].[tld]\".\n          *\n          * TODO: Consider the old publicsuffix code again. This TLD list has\n          * shrunk and shrunk over the years, and has become a poorer and\n          * poorer approximation of administrative boundaries.\n          */\n         const char *const tlds[] = {\"bd\",\"bn\",\"ck\",\"cy\",\"er\",\"fj\",\"fk\",\n                                     \"gu\",\"jm\",\"ke\",\"kh\",\"kw\",\"mm\",\"mz\",\n                                     \"ni\",\"np\",\"pg\",\"ye\",\"za\",\"zw\"};\n         uint_t i, tld_num = sizeof(tlds) / sizeof(tlds[0]);\n\n         for (i = 0; i < tld_num; i++) {\n            if (strlen(tlds[i]) == (uint_t) tld_len &&\n                !dStrnAsciiCasecmp(tlds[i], host + start, tld_len)) {\n               _MSG(\"TLD code matched %s\\n\", tlds[i]);\n               ret++;\n               break;\n            }\n         }\n      }\n   }\n   return ret;\n}\n\n/*\n * Given a URL host string, return the portion that is public, i.e., the\n * domain that is in a registry outside the organization.\n * For 'www.dillo.org', that would be 'dillo.org'.\n */\nstatic const char *Url_host_find_public_suffix(const char *host)\n{\n   const char *s;\n   uint_t dots;\n\n   if (a_Url_host_type(host) != URL_HOST_NAME)\n      return host;\n\n   s = host;\n\n   while (s[1])\n      s++;\n\n   if (s > host && *s == '.') {\n      /* don't want to deal with trailing dot */\n      s--;\n   }\n\n   dots = Url_host_public_internal_dots(host);\n\n   /* With a proper host string, we should not be pointing to a dot now. */\n\n   while (s > host) {\n      if (s[-1] == '.') {\n         if (dots == 0)\n            break;\n         else\n            dots--;\n      }\n      s--;\n   }\n\n   _MSG(\"public suffix of %s is %s\\n\", host, s);\n   return s;\n}\n\nbool_t a_Url_same_organization(const DilloUrl *u1, const DilloUrl *u2)\n{\n   if (!u1 || !u2)\n      return FALSE;\n\n   return dStrAsciiCasecmp(Url_host_find_public_suffix(URL_HOST(u1)),\n                           Url_host_find_public_suffix(URL_HOST(u2)))\n          ? FALSE : TRUE;\n}\n"
  },
  {
    "path": "src/url.h",
    "content": "/*\n * File : url.h - Dillo\n *\n * Copyright (C) 2001 Jorge Arellano Cid <jcid@dillo.org>\n *\n * Parse and normalize all URL's inside Dillo.\n */\n\n#ifndef __URL_H__\n#define __URL_H__\n\n#include \"d_size.h\"\n#include \"../dlib/dlib.h\"\n\n\n#define URL_HTTP_PORT        80\n#define URL_HTTPS_PORT       443\n\n/* for a_Url_host_type() */\n#define URL_HOST_ERROR      -1\n#define URL_HOST_NAME        0\n#define URL_HOST_IPV4        1\n#define URL_HOST_IPV6        2\n\n/*\n * Values for DilloUrl->flags.\n * Specifies which which action to perform with an URL.\n */\n#define URL_Get                 (1 << 0)\n#define URL_Post                (1 << 1)\n\n#define URL_E2EQuery            (1 << 5)\n#define URL_ReloadPage          (1 << 7)\n#define URL_ReloadFromCache     (1 << 8)\n\n#define URL_IgnoreScroll        (1 << 9)\n#define URL_SpamSafe            (1 << 10)\n\n#define URL_MultipartEnc        (1 << 11)\n\n/*\n * Access methods to fields inside DilloURL.\n * (non '_'-ended macros MUST use these for initialization sake)\n */\n/* these MAY return NULL: */\n#define URL_SCHEME_(u)              (u)->scheme\n#define URL_AUTHORITY_(u)           (u)->authority\n#define URL_PATH_(u)                (u)->path\n#define URL_QUERY_(u)               (u)->query\n#define URL_FRAGMENT_(u)            (u)->fragment\n#define URL_HOST_(u)                a_Url_hostname(u)\n#define URL_STR_(u)                 a_Url_str(u)\n/* this returns a Dstr* */\n#define URL_DATA_(u)                (u)->data\n/* these return an integer */\n#define URL_PORT_(u)                (URL_HOST(u), (u)->port)\n#define URL_FLAGS_(u)               (u)->flags\n#define URL_ILLEGAL_CHARS_(u)       (u)->illegal_chars\n#define URL_ILLEGAL_CHARS_SPC_(u)   (u)->illegal_chars_spc\n\n/*\n * Access methods that never return NULL.\n * When the \"empty\" and \"undefined\" concepts of RFC-2396 are irrelevant to\n * the caller, and a string is required, use these methods instead:\n */\n#define NPTR2STR(p)                 ((p) ? (p) : \"\")\n#define URL_SCHEME(u)               NPTR2STR(URL_SCHEME_(u))\n#define URL_AUTHORITY(u)            NPTR2STR(URL_AUTHORITY_(u))\n#define URL_PATH(u)                 NPTR2STR(URL_PATH_(u))\n#define URL_QUERY(u)                NPTR2STR(URL_QUERY_(u))\n#define URL_FRAGMENT(u)             NPTR2STR(URL_FRAGMENT_(u))\n#define URL_HOST(u)                 NPTR2STR(URL_HOST_(u))\n#define URL_STR(u)                  NPTR2STR(URL_STR_(u))\n#define URL_DATA(u)                 URL_DATA_(u)\n#define URL_PORT(u)                 URL_PORT_(u)\n#define URL_FLAGS(u)                URL_FLAGS_(u)\n#define URL_ILLEGAL_CHARS(u)        URL_ILLEGAL_CHARS_(u)\n#define URL_ILLEGAL_CHARS_SPC(u)    URL_ILLEGAL_CHARS_SPC_(u)\n\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\ntypedef struct {\n   Dstr  *url_string;\n   const char *buffer;\n   const char *scheme;            /**/\n   const char *authority;         /**/\n   const char *path;              /* These are references only */\n   const char *query;             /* (no need to free them) */\n   const char *fragment;          /**/\n   const char *hostname;          /**/\n   int port;\n   int flags;\n   Dstr *data;                    /* POST */\n   int ismap_url_len;             /* Used by server side image maps */\n   int illegal_chars;             /* number of illegal chars */\n   int illegal_chars_spc;         /* number of illegal space chars */\n} DilloUrl;\n\n\nDilloUrl* a_Url_new(const char *url_str, const char *base_url);\nvoid a_Url_free(DilloUrl *u);\nchar *a_Url_str(const DilloUrl *url);\nconst char *a_Url_hostname(const DilloUrl *u);\nDilloUrl* a_Url_dup(const DilloUrl *u);\nint a_Url_cmp(const DilloUrl *A, const DilloUrl *B);\nvoid a_Url_set_flags(DilloUrl *u, int flags);\nvoid a_Url_set_data(DilloUrl *u, Dstr **data);\nvoid a_Url_set_ismap_coords(DilloUrl *u, char *coord_str);\nchar *a_Url_decode_hex_str(const char *str);\nchar *a_Url_encode_hex_str(const char *str);\nchar *a_Url_string_strip_delimiters(const char *str);\nint a_Url_host_type(const char *host);\nbool_t a_Url_same_organization(const DilloUrl *u1, const DilloUrl *u2);\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __URL_H__ */\n"
  },
  {
    "path": "src/utf8.cc",
    "content": "/*\n * File: utf8.c\n *\n * Copyright (C) 2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <FL/fl_utf8.h>\n\n#include \"../dlib/dlib.h\"    /* TRUE/FALSE */\n#include \"utf8.hh\"\n\n// C++ functions with C linkage ----------------------------------------------\n\n/*\n * Return index of the last byte of the UTF-8-encoded character that str + i\n * points to or into.\n */\nuint_t a_Utf8_end_of_char(const char *str, uint_t i)\n{\n   /* We can almost get what we want from utf8fwd(p+1,...)-1, but that\n    * does not work for the last character in a string, and the fn makes some\n    * assumptions that do not suit us.\n    * Here's something very simpleminded instead:\n    */\n   if (str && *str && (str[i] & 0x80)) {\n      int internal_bytes = (str[i] & 0x40) ? 0 : 1;\n\n      while (((str[i + 1] & 0xc0) == 0x80) && (++internal_bytes < 4))\n         i++;\n   }\n   return i;\n}\n\n/*\n * Decode a single UTF-8-encoded character starting at p.\n * The resulting Unicode value (in the range 0-0x10ffff) is returned,\n * and len is set to the number of bytes in the UTF-8 encoding.\n * Note that utf8decode(), if given non-UTF-8 data, will interpret\n * it as ISO-8859-1 or CP1252 if possible.\n */\nuint_t a_Utf8_decode(const char* str, const char* end, int* len)\n{\n   return fl_utf8decode(str, end, len);\n}\n\n/*\n * Write UTF-8 encoding of ucs into buf and return number of bytes written.\n */\nint a_Utf8_encode(unsigned int ucs, char *buf)\n{\n   return fl_utf8encode(ucs, buf);\n}\n\n/*\n * Examine first srclen bytes of src.\n * Return 0 if not legal UTF-8, 1 if all ASCII, 2 if all below 0x800,\n * 3 if all below 0x10000, and 4 otherwise.\n */\nint a_Utf8_test(const char* src, unsigned int srclen)\n{\n   return fl_utf8test(src, srclen);\n}\n\n/*\n * Does s point to a UTF-8-encoded ideographic character?\n *\n * This is based on http://unicode.org/reports/tr14/#ID plus some guesses\n * for what might make the most sense for Dillo. Surprisingly, they include\n * Hangul Compatibility Jamo, but they're the experts, so I'll follow along.\n */\nbool_t a_Utf8_ideographic(const char *s, const char *end, int *len)\n{\n   bool_t ret = FALSE;\n\n   if ((uchar_t)*s >= 0xe2) {\n      /* Unicode char >= U+2000. */\n      unsigned unicode = a_Utf8_decode(s, end, len);\n\n      if (unicode >= 0x2e80 &&\n           ((unicode <= 0xa4cf) ||\n            (unicode >= 0xf900 && unicode <= 0xfaff) ||\n            (unicode >= 0xff00 && unicode <= 0xff9f))) {\n         ret = TRUE;\n     }\n   } else {\n      *len = 1 + (int)a_Utf8_end_of_char(s, 0);\n   }\n   return ret;\n}\n\nbool_t a_Utf8_combining_char(int unicode)\n{\n   return ((unicode >= 0x0300 && unicode <= 0x036f) ||\n           (unicode >= 0x1dc0 && unicode <= 0x1dff) ||\n           (unicode >= 0x20d0 && unicode <= 0x20ff) ||\n           (unicode >= 0xfe20 && unicode <= 0xfe2f));\n}\n\nint a_Utf8_char_count(const char *str, int len)\n{\n   return fl_utf_nb_char((const uchar_t*)str, len);\n}\n"
  },
  {
    "path": "src/utf8.hh",
    "content": "#ifndef __UTF8_HH__\n#define __UTF8_HH__\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n\n#include \"d_size.h\"\n\n/*\n * Unicode replacement character U+FFFD\n * \"used to replace an incoming character whose value is unknown or otherwise\n * unrepresentable in Unicode\"\n */\nstatic const char utf8_replacement_char[] = \"\\xEF\\xBF\\xBD\";\n\n/* Unicode zero width space U+200B */\nstatic const char utf8_zero_width_space[] = \"\\xE2\\x80\\x8B\";\n\nuint_t a_Utf8_end_of_char(const char *str, uint_t i);\nuint_t a_Utf8_decode(const char*, const char* end, int* len);\nint a_Utf8_encode(unsigned int ucs, char *buf);\nint a_Utf8_test(const char* src, unsigned int srclen);\nbool_t a_Utf8_ideographic(const char *s, const char *end, int *len);\nbool_t a_Utf8_combining_char(int unicode);\nint a_Utf8_char_count(const char *str, int len);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n\n#endif /* __UTF8_HH__ */\n\n"
  },
  {
    "path": "src/web.cc",
    "content": "/*\n * File: web.cc\n *\n * Copyright 2005-2007 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include \"msg.h\"\n#include \"nav.h\"\n\n#include \"uicmd.hh\"\n\n#include \"IO/IO.h\"\n#include \"IO/mime.h\"\n\n#include \"dw/core.hh\"\n#include \"styleengine.hh\"\n#include \"web.hh\"\n\n// Platform independent part\nusing namespace dw::core;\n\n\n/*\n * Local data\n */\nstatic Dlist *ValidWebs;      /* Active web structures list; it holds\n                               * pointers to DilloWeb structures. */\n\n/*\n * Initialize local data\n */\nvoid a_Web_init(void)\n{\n   ValidWebs = dList_new(32);\n}\n\n/*\n * Given the MIME content type, and a fd to read it from,\n * this function connects the proper MIME viewer to it.\n * Return value:\n *    1 on success (and Call and Data properly set).\n *   -1 for unhandled MIME types (and Call and Data untouched).\n */\nint a_Web_dispatch_by_type (const char *Type, DilloWeb *Web,\n                            CA_Callback_t *Call, void **Data)\n{\n   Widget *dw = NULL;\n\n   _MSG(\"a_Web_dispatch_by_type\\n\");\n\n   dReturn_val_if_fail(Web->bw != NULL, -1);\n\n   Layout *layout = (Layout*)Web->bw->render_layout;\n\n   Viewer_t viewer = a_Mime_get_viewer(Type);\n   if (viewer == NULL)\n      return -1;\n\n   if (Web->flags & WEB_RootUrl) {\n      /* We have RootUrl! */\n\n      style::Color *bgColor = style::Color::create (layout, prefs.bg_color);\n      Web->bgColor = bgColor->getColor ();\n      layout->setBgColor (bgColor);\n      layout->setBgImage (NULL, style::BACKGROUND_REPEAT,\n                          style::BACKGROUND_ATTACHMENT_SCROLL,\n                          style::createPerLength (0),\n                          style::createPerLength (0));\n\n      /* Set a style for the widget */\n      StyleEngine styleEngine (layout, Web->url, Web->url);\n      styleEngine.startElement (\"body\", Web->bw);\n\n      dw = (Widget*) viewer(Type, Web, Call, Data);\n      if (dw == NULL)\n         return -1;\n\n      dw->setStyle (styleEngine.style (Web->bw));\n\n      /* This method frees the old dw if any */\n      layout->setWidget(dw);\n\n      /* Set the page title with the bare filename (e.g. for images),\n       * HTML pages with a <TITLE> tag will overwrite it later */\n      const char *p = strrchr(URL_STR(Web->url), '/');\n      a_UIcmd_set_page_title(Web->bw, p ? p+1 : \"\");\n\n      a_UIcmd_set_location_text(Web->bw, URL_STR(Web->url));\n      /* Reset both progress bars */\n      a_UIcmd_set_page_prog(Web->bw, 0, 2);\n      a_UIcmd_set_img_prog(Web->bw, 0, 0, 2);\n      /* Reset the bug meter */\n      a_UIcmd_set_bug_prog(Web->bw, 0);\n\n      /* Let the Nav module know... */\n      a_Nav_expect_done(Web->bw);\n\n   } else {\n      /* A non-RootUrl. At this moment we only handle image-children */\n      if (!dStrnAsciiCasecmp(Type, \"image/\", 6)) {\n         dw = (Widget*) viewer(Type, Web, Call, Data);\n      } else {\n         MSG_HTTP(\"'%s' cannot be displayed as image; has media type '%s'\\n\",\n                  URL_STR(Web->url), Type);\n      }\n   }\n   return (dw ? 1 : -1);\n}\n\n\n/*\n * Allocate and set safe values for a DilloWeb structure\n */\nDilloWeb* a_Web_new(BrowserWindow *bw, const DilloUrl *url,\n                    const DilloUrl *requester)\n{\n   DilloWeb *web= dNew(DilloWeb, 1);\n\n   _MSG(\" a_Web_new: ValidWebs ==> %d\\n\", dList_length(ValidWebs));\n   web->url = a_Url_dup(url);\n   web->requester = a_Url_dup(requester);\n   web->bw = bw;\n   web->flags = 0;\n   web->Image = NULL;\n   web->filename = NULL;\n   web->stream = NULL;\n   web->SavedBytes = 0;\n   web->bgColor = 0x000000; /* Dummy value will be overwritten\n                             * in a_Web_dispatch_by_type. */\n   dList_append(ValidWebs, (void *)web);\n   return web;\n}\n\n/*\n * Validate a DilloWeb pointer\n */\nint a_Web_valid(DilloWeb *web)\n{\n   return (dList_find(ValidWebs, web) != NULL);\n}\n\n/*\n * Deallocate a DilloWeb structure\n */\nvoid a_Web_free(DilloWeb *web)\n{\n   if (!web) return;\n   a_Url_free(web->url);\n   a_Url_free(web->requester);\n   a_Image_unref(web->Image);\n   dFree(web->filename);\n   dList_remove(ValidWebs, (void *)web);\n   _MSG(\"a_Web_free: ValidWebs=%d\\n\", dList_length(ValidWebs));\n   dFree(web);\n}\n\n"
  },
  {
    "path": "src/web.hh",
    "content": "#ifndef __WEB_H__\n#define __WEB_H__\n\n#include <stdio.h>     /* for FILE */\n#include \"bw.h\"        /* for BrowserWindow */\n#include \"cache.h\"     /* for CA_Callback_t */\n#include \"image.hh\"    /* for DilloImage */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* __cplusplus */\n\n/*\n * Flag defines\n */\n#define WEB_RootUrl  1\n#define WEB_Image    2\n#define WEB_Stylesheet 4\n#define WEB_Download 8   /* Half implemented... */\n\n\ntypedef struct _DilloWeb DilloWeb;\n\nstruct _DilloWeb {\n  DilloUrl *url;              /* Requested URL */\n  DilloUrl *requester;        /* URL that caused this request, or\n                               * NULL if user-initiated. */\n  BrowserWindow *bw;          /* The requesting browser window [reference] */\n  int flags;                  /* Additional info */\n\n  DilloImage *Image;          /* For image urls [reference] */\n\n  int32_t bgColor;            /* for image backgrounds */\n  char *filename;             /* Variables for Local saving */\n  FILE *stream;\n  int SavedBytes;\n};\n\nvoid a_Web_init(void);\nDilloWeb* a_Web_new (BrowserWindow *bw, const DilloUrl* url,\n                     const DilloUrl *requester);\nint a_Web_valid(DilloWeb *web);\nvoid a_Web_free (DilloWeb*);\nint a_Web_dispatch_by_type (const char *Type, DilloWeb *web,\n                             CA_Callback_t *Call, void **Data);\n\n#ifdef __cplusplus\n}\n#endif /* __cplusplus */\n#endif /* __WEB_H__ */\n"
  },
  {
    "path": "src/xembed.cc",
    "content": "/*\n * File: xembed.cc\n *\n * Copyright (C) 2009 Jorge Arellano Cid <jcid@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n */\n\n#include <string.h>\n#include <ctype.h>\n\n#define FL_INTERNALS\n#include <FL/Fl_Window.H>\n#include <FL/Fl.H>\n\n#include \"xembed.hh\"\n\n#if !( defined(DISABLE_XEMBED) || defined(WIN32) || defined(__APPLE__) )\n#include <FL/x.H>\n\ntypedef enum {\n  XEMBED_EMBEDDED_NOTIFY        = 0,\n  XEMBED_WINDOW_ACTIVATE        = 1,\n  XEMBED_WINDOW_DEACTIVATE      = 2,\n  XEMBED_REQUEST_FOCUS          = 3,\n  XEMBED_FOCUS_IN               = 4,\n  XEMBED_FOCUS_OUT              = 5,\n  XEMBED_FOCUS_NEXT             = 6,\n  XEMBED_FOCUS_PREV             = 7,\n  XEMBED_GRAB_KEY               = 8,\n  XEMBED_UNGRAB_KEY             = 9,\n  XEMBED_MODALITY_ON            = 10,\n  XEMBED_MODALITY_OFF           = 11,\n} XEmbedMessageType;\n\nvoid\nXembed::setXembedInfo(unsigned long flags)\n{\n  unsigned long buffer[2];\n\n  Atom xembed_info_atom = XInternAtom (fl_display, \"_XEMBED_INFO\", false);\n\n  buffer[0] = 1;\n  buffer[1] = flags;\n\n  XChangeProperty (fl_display,\n     xid,\n     xembed_info_atom, xembed_info_atom, 32,\n     PropModeReplace,\n     (unsigned char *)buffer, 2);\n}\n\nvoid\nXembed::sendXembedEvent(uint32_t message) {\n   XClientMessageEvent xclient;\n\n   memset (&xclient, 0, sizeof (xclient));\n   xclient.window = xid;\n   xclient.type = ClientMessage;\n   xclient.message_type = XInternAtom (fl_display, \"_XEMBED\", false);\n   xclient.format = 32;\n   xclient.data.l[0] = fl_event_time;\n   xclient.data.l[1] = message;\n\n   XSendEvent(fl_display, xid, False, NoEventMask, (XEvent *)&xclient);\n   XSync(fl_display, False);\n}\n\nint\nXembed::handle(int e) {\n   if (e == FL_PUSH)\n      sendXembedEvent(XEMBED_REQUEST_FOCUS);\n\n   return Fl_Window::handle(e);\n}\n\nstatic int event_handler(int e, Fl_Window *w) {\n   Atom xembed_atom = XInternAtom (fl_display, \"_XEMBED\", false);\n\n   if (fl_xevent->type == ClientMessage) {\n      if (fl_xevent->xclient.message_type == xembed_atom) {\n         long message = fl_xevent->xclient.data.l[1];\n\n         switch (message) {\n            case XEMBED_WINDOW_ACTIVATE:\n               // Force a ConfigureNotify message so fltk can get the new\n               // coordinates after a move of the embedder window.\n               if (w)\n                  w->resize(0,0, w->w(), w->h());\n               break;\n            case XEMBED_WINDOW_DEACTIVATE:\n               break;\n            default:\n               break;\n         }\n      }\n   }\n\n   return Fl::handle_(e, w);\n}\n\n// TODO: Implement more XEMBED support;\n\nvoid Xembed::show() {\n   createInternal(xid);\n   setXembedInfo(1);\n   Fl::event_dispatch(event_handler);\n   Fl_Window::show();\n}\n\nvoid Xembed::createInternal(uint32_t parent) {\n   Fl_Window *window = this;\n   Colormap colormap = fl_colormap;\n\n   XSetWindowAttributes attr;\n   attr.border_pixel = 0;\n   attr.colormap = colormap;\n   attr.bit_gravity = 0; // StaticGravity;\n   int mask = CWBorderPixel|CWColormap|CWEventMask|CWBitGravity;\n\n   int W = window->w();\n   if (W <= 0) W = 1; // X don't like zero...\n   int H = window->h();\n   if (H <= 0) H = 1; // X don't like zero...\n   int X = window->x();\n   int Y = window->y();\n\n   attr.event_mask =\n      ExposureMask | StructureNotifyMask\n      | KeyPressMask | KeyReleaseMask | KeymapStateMask | FocusChangeMask\n      | ButtonPressMask | ButtonReleaseMask\n      | EnterWindowMask | LeaveWindowMask\n      | PointerMotionMask;\n\n   Fl_X::set_xid(window,\n      XCreateWindow(fl_display,\n         parent,\n         X, Y, W, H,\n         0, // borderwidth\n         fl_visual->depth,\n         InputOutput,\n         fl_visual->visual,\n         mask, &attr));\n}\n\n#else  // X_PROTOCOL\n\nvoid\nXembed::setXembedInfo(unsigned long flags) {};\n\nvoid\nXembed::sendXembedEvent(uint32_t message) {};\n\nint\nXembed::handle(int e) {\n   return Fl_Window::handle(e);\n}\n\nvoid\nXembed::show() {\n   Fl_Window::show();\n}\n\n#endif\n"
  },
  {
    "path": "src/xembed.hh",
    "content": "#ifndef __XEMBED_HH__\n#define __XEMBED_HH__\n\n#include <FL/Fl_Window.H>\n\n#include \"d_size.h\"\n\nclass Xembed : public Fl_Window {\n   private:\n      uint32_t xid;\n      void createInternal(uint32_t parent);\n      void setXembedInfo(unsigned long flags);\n      void sendXembedEvent(uint32_t message);\n\n   public:\n      Xembed(uint32_t xid, int _w, int _h) : Fl_Window(_w, _h) {\n         this->xid = xid;\n      };\n      void show();\n      int handle(int event);\n};\n\n#endif\n"
  },
  {
    "path": "style.css",
    "content": "h1, h2, h3, h4, h5, h6, h7, h8, h9 {\n  color: black;\n  line-height:1.2\n}\n\na {\n  color: #004b6b;\n  text-decoration: none;\n}\n\na:hover {\n  color: #6d4100;\n}\n\nblockquote {\n  font-style: italic;\n}\n\nbody {\n  font-family: 'Garamond', 'Georgia', serif;\n  font-size: 17px;\n  background-color: #fff;\n  color: #3e4349;\n  margin: 0 15px 0 15px;\n  padding: 0;\n}\n"
  },
  {
    "path": "style.css.orig.full",
    "content": "/* source: https://oldforum.puppylinux.com/viewtopic.php?t=70227&sid=1d459a178edf1c0d4f05e04b0d4ec165 */\n\nbody {background-color: #e0e0a3; font-family: sans-serif; color: black; margin: 5px}\n\nbig {font-size: 1.17em}\n\nblockquote, dd {margin-left: 40px; margin-right: 40px}\n\ncenter {text-align: center}\n\ndt {font-weight: bolder}\n\n:link {color: blue; text-decoration: underline; cursor: pointer}\n\n:visited {color: #800080; text-decoration: underline; cursor: pointer}\n\nh1, h2, h3, h4, h5, h6, b, strong {font-weight: bolder}\n\ni, em, cite, address, var {font-style: italic}\n\n:link img, :visited img {border: 1px solid}\n\nframeset, ul, ol, dir {margin-left: 40px}\n\nh1 {font-size: 2em; margin-top: .67em; margin-bottom: 0}\n\nh2 {font-size: 1.5em; margin-top: .75em; margin-bottom: 0}\n\nh3 {font-size: 1.17em; margin-top: .83em; margin-bottom: 0}\n\nh4 {margin-top: 1.12em; margin-bottom: 0}\n\nh5 {font-size: 0.83em; margin-top: 1.5em; margin-bottom: 0}\n\nh6 {font-size: 0.75em; margin-top: 1.67em; margin-bottom: 0}\n\nhr {width: 100%; border: 1px inset}\n\nli {margin-top: 0.1em}\n\npre {white-space: pre}\n\nol {list-style-type: decimal}\n\nul {list-style-type: disc}\n\nul ul {list-style-type: circle}\n\nul ul ul {list-style-type: square}\n\nul ul ul ul {list-style-type: disc}\n\nu {text-decoration: underline}\n\nsmall, sub, sup {font-size: 0.83em}\n\nsub {vertical-align: sub}\n\nsup {vertical-align: super}\n\ns, strike, del {text-decoration: line-through}\n\ntable {border-style: outset; border-spacing: 1px}\n\ntd, th {border-style: inset; padding: 2px}\n\nthead, tbody, tfoot {vertical-align: middle}\n\nth {font-weight: bolder; text-align: center}\n\ncode, tt, pre, samp, kbd {font-family: monospace}\n\ntable, caption {font-size: medium; font-weight: normal}"
  },
  {
    "path": "style_reader_mode.css",
    "content": "html {\n  text-align: center !important;\n}\n\nbody {\n  display: inline-block !important;\n  font-size: 19px !important;\n  max-width: 800px !important;\n}\n\nbutton, input, div, pre, ul, dl, ol, table, main, section, header, article, aside, footer, nav, span, em, b, a, form, h1, h2, h3, h4, h5, h6, h7, h8, h9 {\n  text-align: left !important;\n  max-width: 800px !important;\n  margin: auto !important;\n}\n\nbutton, input, label {\n  display: inline !important;\n}\n\nbutton *, input *, label * {\n  display: inline !important;\n}\n\nh1, h2, h3, h4, h5, h6, h7, h8, h9 {\n  padding: 16px 0 16px 0;\n}\n\np, q, blockquote {\n  text-align: justify !important;\n  max-width: 800px !important;\n  margin: auto !important;\n  padding: 10px 0 10px 0;\n}\n\nimg, figure, figcaption {\n  max-width: 800px !important;\n  max-height: auto !important;\n  width: auto !important;\n  height: auto !important;\n  margin: auto !important;\n}\n"
  },
  {
    "path": "test/Anna_Karenina_1.html",
    "content": "<div lang=\"ru\" style=\"text-indent: 0.7cm; text-align: justify\">\n<p>Все счастливые семьи похожи друг на друга, каждая несчастливая семья несчастлива по-своему.</p>\n<p>Все смешалось в доме Облонских. Жена узнала, что муж был в связи с бывшею в их доме француженкою-гувернанткой, и объявила мужу, что не может жить с ним в одном доме. Положение это продолжалось уже третий день и мучительно чувствовалось и самими супругами, и всеми членами семьи, и домочадцами. Все члены семьи и домочадцы чувствовали, что нет смысла в их сожительстве и что на каждом постоялом дворе случайно сошедшиеся люди более связаны между собой, чем они, члены семьи и домочадцы Облонских. Жена не выходила из своих комнат, мужа третий день не было дома. Дети бегали по всему дому, как потерянные; англичанка поссорилась с экономкой и написала записку приятельнице, прося приискать ей новое место; повар ушел вчера со двора, во время самого обеда; черная кухарка и кучер просили расчета.</p>\n<p>На третий день после ссоры князь Степан Аркадьич Облонский&#160;— Стива, как его звали в свете,&#160;— в обычный час, то есть в восемь часов утра, проснулся не в спальне жены, а в своем кабинете, на сафьянном диване. Он повернул свое полное, выхоленное тело на пружинах дивана, как бы желая опять заснуть надолго, с другой стороны крепко обнял подушку и прижался к ней щекой; но вдруг вскочил, сел на диван и открыл глаза.</p>\n<p>«Да, да, как это было?&#160;— думал он, вспоминая сон.&#160;— Да,\nкак это было? Да! Алабин давал обед в Дармштадте; нет, не в\nДармштадте, а что-то американское. Да, но там Дармштадт был в\nАмерике. Да, Алабин давал обед на стеклянных столах, да,&#160;— и\nстолы пели: <span lang=\"it\">Il mio tesoro</span>, и\nне <span lang=\"it\">Il mio tesoro</span>, а что-то лучше, и какие-то\nмаленькие графинчики, и они же женщины»,&#160;— вспоминал он.</p>\n\n<p>Глаза Степана Аркадьича весело заблестели, и он задумался, улыбаясь. «Да, хорошо было, очень хорошо. Много еще что-то там было отличного, да не скажешь словами и мыслями даже наяву не выразишь». И, заметив полосу света, пробившуюся сбоку одной из суконных стор, он весело скинул ноги с дивана, отыскал ими шитые женой (подарок ко дню рождения в прошлом году), обделанные в золотистый сафьян туфли и по старой, девятилетней привычке, не вставая, потянулся рукой к тому месту, где в спальне у него висел халат. И тут он вспомнил вдруг, как и почему он спит не в спальне жены, а в кабинете; улыбка исчезла с его лица, он сморщил лоб.</p>\n<p>«Ах, ах, ах! Ааа!..»&#160;— замычал он, вспоминая все, что было. И его воображению представились опять все подробности ссоры с женою, вся безвыходность его положения и мучительнее всего собственная вина его.</p>\n<p>«Да! она не простит и не может простить. И всего ужаснее то, что виной всему я, виной я, а не виноват. В этом-то вся драма,&#160;— думал он.&#160;— Ах, ах, ах!»&#160;— приговаривал он с отчаянием, вспоминая самые тяжелые для себя впечатления из этой ссоры.</p>\n<p>Неприятнее всего была та первая минута, когда он, вернувшись из театра, веселым и довольным, с огромною грушей для жены в руке, не нашел жены в гостиной; к удивлению, не нашел ее и в кабинете и, наконец, увидал ее в спальне с несчастною, открывшею все, запиской в руке.</p>\n<p>Она, эта вечно озабоченная, и хлопотливая, и недалекая, какою он считал ее, Долли, неподвижно сидела с запиской в руке и с выражением ужаса, отчаяния и гнева смотрела на него.</p>\n<p>— Что это? это?&#160;— спрашивала она, указывая на записку.</p>\n\n<p>И при этом воспоминании, как это часто бывает, мучало Степана Аркадьича не столько самое событие, сколько то, как он ответил на эти слова жены.</p>\n<p>С ним случилось в эту минуту то, что случается с людьми, когда они неожиданно уличены в чем-нибудь слишком постыдном. Он не сумел приготовить свое лицо к тому положению, в которое он становился пред женой после открытия его вины. Вместо того чтоб оскорбиться, отрекаться, оправдываться, просить прощения, оставаться даже равнодушным&#160;— все было бы лучше того, что он сделал!&#160;— его лицо совершенно невольно («рефлексы головного мозга»,&#160;— подумал Степан Аркадьич, который любил физиологию), совершенно невольно вдруг улыбнулось привычною, доброю и потому глупою улыбкой.</p>\n<p>Эту глупую улыбку он не мог простить себе. Увидав эту улыбку, Долли вздрогнула, как от физической боли, разразилась, со свойственною ей горячностью, потоком жестоких слов и выбежала из комнаты. С тех пор она не хотела видеть мужа.</p>\n<p>«Всему виной эта глупая улыбка»,&#160;— думал Степан Аркадьич.</p>\n<p>«Но что ж делать? что ж делать?»&#160;— с отчаянием говорил он себе и не находил ответа.</p>\n</div>\n"
  },
  {
    "path": "test/KHM1-shy.html",
    "content": "<div style=\"text-align: justify\">\n<p>In den al&shy;ten Zei&shy;ten, wo das Wün&shy;schen noch\nge&shy;hol&shy;fen hat, leb&shy;te ein Kö&shy;nig, des&shy;sen\nTöch&shy;ter wa&shy;ren al&shy;le schön, aber die jüng&shy;ste war so\nschön, daß die Son&shy;ne sel&shy;ber, die doch so vie&shy;les\nge&shy;se&shy;hen hat, sich ver&shy;wun&shy;der&shy;te so oft sie ihr\nins Ge&shy;sicht schien. Na&shy;he bei dem Schlos&shy;se des\nKö&shy;nigs lag ein gro&shy;ßer dunk&shy;ler Wald, und in dem\nWal&shy;de un&shy;ter ei&shy;ner al&shy;ten Lin&shy;de war ein\nBrun&shy;nen: wenn nun der Tag recht heiß war, so ging das\nKö&shy;nigs&shy;kind hin&shy;aus in den Wald und setz&shy;te sich an\nden Rand des küh&shy;len Brun&shy;nens: und wenn sie\nLan&shy;ge&shy;wei&shy;le hat&shy;te, so nahm sie eine\ngol&shy;de&shy;ne Ku&shy;gel, warf sie in die Hö&shy;he und fieng sie\nwie&shy;der; und das war ihr liebs&shy;tes Spiel&shy;werk.</p>\n<p>Nun trug es sich ein&shy;mal zu, daß die gol&shy;de&shy;ne\nKu&shy;gel der Kön&shy;igs&shy;toch&shy;ter nicht in ihr Händ&shy;chen\nfiel, das sie in die Hö&shy;he ge&shy;hal&shy;ten hat&shy;te,\nson&shy;dern vor&shy;bei auf die Er&shy;de schlug und\nge&shy;ra&shy;de&shy;zu ins Was&shy;ser hin&shy;ein roll&shy;te. Die\nKö&shy;nigs&shy;toch&shy;ter folg&shy;te ihr mit den Aug&shy;en nach,\naber die Ku&shy;gel ver&shy;schwand, und der Brun&shy;nen war tief, so\ntief daß man kei&shy;nen Grund sah. Da fieng sie an zu wei&shy;nen und\nwein&shy;te im&shy;mer lau&shy;ter und konn&shy;te sich gar nicht\ntrös&shy;ten. Und wie sie so klag&shy;te, rief ihr je&shy;mand zu „was\nhast du vor, Kö&shy;nigs&shy;toch&shy;ter, du schreist ja daß sich ein\nStein er&shy;bar&shy;men möchte.“ Sie sah sich um, wo&shy;her die\nStim&shy;me kä&shy;me, da er&shy;blick&shy;te sie einen Frosch, der\nsei&shy;nen di&shy;cken häß&shy;li&shy;chen Kopf aus dem Was&shy;ser\nstreck&shy;te. „Ach, du bists, al&shy;ter\nWas&shy;ser&shy;pat&shy;scher,“ sag&shy;te sie, „ich wei&shy;ne über\nmei&shy;ne gol&shy;de&shy;ne Ku&shy;gel, die mir in den Brun&shy;nen\nhin&shy;ab ge&shy;fal&shy;len ist.“ „Sei still und wei&shy;ne nicht,“\nant&shy;wor&shy;te&shy;te der Frosch, „ich kann wohl Rath\nschaf&shy;fen, aber was gibst du mir, wenn ich dein Spiel&shy;werk\nwie&shy;der her&shy;auf&shy;ho&shy;le?“ „Was du ha&shy;ben willst,\nlie&shy;ber Frosch,“ sag&shy;te sie, „mei&shy;ne Klei&shy;der,\nmei&shy;ne Per&shy;len und Edel&shy;stei&shy;ne, auch noch die\ngol&shy;de&shy;ne Kro&shy;ne, die ich tra&shy;ge.“ Der Frosch\nant&shy;wor&shy;te&shy;te „dei&shy;ne Klei&shy;der, dei&shy;ne\nPer&shy;len und Edel&shy;stei&shy;ne, und dei&shy;ne gol&shy;de&shy;ne\nKro&shy;ne, die mag ich nicht: aber wenn du mich lieb ha&shy;ben\nwillst, und ich soll dein Ge&shy;sel&shy;le und\nSpiel&shy;ka&shy;me&shy;rad sein, an dei&shy;nem Tisch&shy;lein\nne&shy;ben dir si&shy;tzen, von dei&shy;nem gol&shy;de&shy;nen\nTel&shy;ler&shy;lein es&shy;sen, aus dei&shy;nem Be&shy;cher&shy;lein\ntrin&shy;ken, in dei&shy;nem Bett&shy;lein schla&shy;fen: wenn du mir\ndas ver&shy;sprichst, so will ich hin&shy;un&shy;ter stei&shy;gen und\ndir die gol&shy;de&shy;ne Ku&shy;gel wie&shy;der her&shy;auf\nho&shy;len.“ „Ach ja,“ sag&shy;te sie, „ich ver&shy;spre&shy;che dir\nalles, was du willst, wenn du mir nur die Ku&shy;gel wie&shy;der\nbringst.“ Sie dach&shy;te aber „was der ein&shy;fäl&shy;ti&shy;ge\nFrosch schwätzt, der sitzt im Was&shy;ser bei sei&shy;nes\nGlei&shy;chen und quackt, und kann kei&shy;nes Men&shy;schen\nGe&shy;sel&shy;le sein.“</p>\n</div>\n"
  },
  {
    "path": "test/KHM1.html",
    "content": "<div style=\"text-align: justify\">\n<p>In den alten Zeiten, wo das Wünschen noch geholfen hat, lebte ein\nKönig, dessen Töchter waren alle schön, aber die jüngste war so schön,\ndaß die Sonne selber, die doch so vieles gesehen hat, sich verwunderte\nso oft sie ihr ins Gesicht schien. Nahe bei dem Schlosse des Königs\nlag ein großer dunkler Wald, und in dem Walde unter einer alten Linde\nwar ein Brunnen: wenn nun der Tag recht heiß war, so ging das\nKönigskind hinaus in den Wald und setzte sich an den Rand des kühlen\nBrunnens: und wenn sie Langeweile hatte, so nahm sie eine goldene\nKugel, warf sie in die Höhe und fieng sie wieder; und das war ihr\nliebstes Spielwerk.</p>\n<p>Nun trug es sich einmal zu, daß die goldene Kugel der Königstochter\nnicht in ihr Händchen fiel, das sie in die Höhe gehalten hatte,\nsondern vorbei auf die Erde schlug und geradezu ins Wasser hinein\nrollte. Die Königstochter folgte ihr mit den Augen nach, aber die\nKugel verschwand, und der Brunnen war tief, so tief daß man keinen\nGrund sah. Da fieng sie an zu weinen und weinte immer lauter und\nkonnte sich gar nicht trösten. Und wie sie so klagte, rief ihr jemand\nzu „was hast du vor, Königstochter, du schreist ja daß sich ein Stein\nerbarmen möchte.“ Sie sah sich um, woher die Stimme käme, da erblickte\nsie einen Frosch, der seinen dicken häßlichen Kopf aus dem Wasser\nstreckte. „Ach, du bists, alter Wasserpatscher,“ sagte sie, „ich weine\nüber meine goldene Kugel, die mir in den Brunnen hinab gefallen ist.“\n„Sei still und weine nicht,“ antwortete der Frosch, „ich kann wohl\nRath schaffen, aber was gibst du mir, wenn ich dein Spielwerk wieder\nheraufhole?“ „Was du haben willst, lieber Frosch,“ sagte sie, „meine\nKleider, meine Perlen und Edelsteine, auch noch die goldene Krone, die\nich trage.“ Der Frosch antwortete „deine Kleider, deine Perlen und\nEdelsteine, und deine goldene Krone, die mag ich nicht: aber wenn du\nmich lieb haben willst, und ich soll dein Geselle und Spielkamerad\nsein, an deinem Tischlein neben dir sitzen, von deinem goldenen\nTellerlein essen, aus deinem Becherlein trinken, in deinem Bettlein\nschlafen: wenn du mir das versprichst, so will ich hinunter steigen\nund dir die goldene Kugel wieder herauf holen.“ „Ach ja,“ sagte sie,\n„ich verspreche dir alles, was du willst, wenn du mir nur die Kugel\nwieder bringst.“ Sie dachte aber „was der einfältige Frosch schwätzt,\nder sitzt im Wasser bei seines Gleichen und quackt, und kann keines\nMenschen Geselle sein.“</p>\n</div>\n"
  },
  {
    "path": "test/KHM1b.html",
    "content": "<p style=\"text-align: justify\">In den al&shy;ten Zei&shy;ten, wo das\nWün&shy;schen noch ge&shy;hol&shy;fen hat, leb&shy;te ein Kö&shy;nig,\ndes&shy;sen Töch&shy;ter wa&shy;ren al&shy;le schön, aber die\njüng&shy;ste war so schön, daß die Son&shy;ne sel&shy;ber, die doch so\nvie&shy;les ge&shy;se&shy;hen hat, sich ver&shy;wun&shy;der&shy;te so\noft sie ihr ins Ge&shy;sicht schien. Na&shy;he bei dem Schlos&shy;se\ndes Kö&shy;nigs lag ein gro&shy;ßer dunk&shy;ler Wald, und in dem\nWal&shy;de un&shy;ter ei&shy;ner al&shy;ten Lin&shy;de war ein\nBrun&shy;nen: wenn nun der Tag recht heiß war, so ging das\nKö&shy;nigs&shy;kind hin&shy;aus in den Wald und setz&shy;te sich an\nden Rand des küh&shy;len Brun&shy;nens: und wenn sie\nLan&shy;ge&shy;wei&shy;le hat&shy;te, so nahm sie eine\ngol&shy;de&shy;ne Ku&shy;gel, warf sie in die Hö&shy;he und fieng sie\nwie&shy;der; und das war ihr liebs&shy;tes Spiel&shy;werk.</p>\n"
  },
  {
    "path": "test/KHM1c.html",
    "content": "<p style=\"text-align: justify\">In den alten Zeiten, wo das Wünschen\nnoch geholfen hat, lebte ein König, dessen Töchter waren alle schön,\naber die jüngste war so schön, daß die Sonne selber, die doch so\nvieles gesehen hat, sich verwunderte so oft sie ihr ins Gesicht\nschien. Nahe bei dem Schlosse des Königs lag ein großer dunkler Wald,\nund in dem Walde unter einer alten Linde war ein Brunnen: wenn nun der\nTag recht heiß war, so ging das Königskind hinaus in den Wald und\nsetzte sich an den Rand des kühlen Brunnens: und wenn sie Langeweile\nhatte, so nahm sie eine goldene Kugel, warf sie in die Höhe und fieng\nsie wieder; und das war ihr liebstes Spielwerk.</p>\n"
  },
  {
    "path": "test/Makefile",
    "content": "include ../Makefile.options\n\nall: dw-anchors-test dw-example dw-find-test dw-float-test dw-links dw-links2 dw-image-background dw-images-simple dw-images-scaled dw-images-scaled2 dw-lists dw-simple-container-test dw-table-aligned dw-table dw-border-test dw-imgbuf-mem-test identity dw-ui-test dw-resource-test containers shapes cookies liang trie notsosimplevector unicode-test\n\ndw_anchors_test.o: dw_anchors_test.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_anchors_test.cc\n\ndw-anchors-test: dw_anchors_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-anchors-test dw_anchors_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_example.o: dw_example.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_example.cc\n\ndw-example: dw_example.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-example dw_example.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_find_test.o: dw_find_test.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_find_test.cc\n\ndw-find-test: dw_find_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-find-test dw_find_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_float_test.o: dw_float_test.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_float_test.cc\n\ndw-float-test: dw_float_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-float-test dw_float_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_links.o: dw_links.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_links.cc\n\ndw-links: dw_links.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-links dw_links.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_links2.o: dw_links2.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_links2.cc\n\ndw-links2: dw_links2.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-links2 dw_links2.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_image_background.o: dw_image_background.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_image_background.cc\n\ndw-image-background: dw_image_background.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-image-background dw_image_background.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_images_simple.o: dw_images_simple.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_images_simple.cc\n\ndw-images-simple: dw_images_simple.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-images-simple dw_images_simple.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_images_scaled.o: dw_images_scaled.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_images_scaled.cc\n\ndw-images-scaled: dw_images_scaled.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-images-scaled dw_images_scaled.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_images_scaled2.o: dw_images_scaled2.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_images_scaled2.cc\n\ndw-images-scaled2: dw_images_scaled2.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-images-scaled2 dw_images_scaled2.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_lists.o: dw_lists.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_lists.cc\n\ndw-lists: dw_lists.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-lists dw_lists.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_simple_container.o: dw_simple_container.cc dw_simple_container.hh\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_simple_container.cc\n\ndw_simple_container_test.o: dw_simple_container_test.cc dw_simple_container.hh\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_simple_container_test.cc\n\ndw-simple-container-test: dw_simple_container_test.o dw_simple_container.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-simple-container-test dw_simple_container_test.o dw_simple_container.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_table_aligned.o: dw_table_aligned.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_table_aligned.cc\n\ndw-table-aligned: dw_table_aligned.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-table-aligned dw_table_aligned.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_table.o: dw_table.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_table.cc\n\ndw-table: dw_table.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-table dw_table.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_border_test.o: dw_border_test.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_border_test.cc\n\ndw-border-test: dw_border_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-border-test dw_border_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_imgbuf_mem_test.o: dw_imgbuf_mem_test.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_imgbuf_mem_test.cc\n\ndw-imgbuf-mem-test: dw_imgbuf_mem_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-imgbuf-mem-test dw_imgbuf_mem_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_resource_test.o: dw_resource_test.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_resource_test.cc\n\ndw-resource-test: dw_resource_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-resource-test dw_resource_test.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw_ui_test.o: dw_ui_test.cc form.hh\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c dw_ui_test.cc\n\nform.o: form.cc form.hh\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c form.cc\n\nidentity.o: identity.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c identity.cc\n\nidentity: identity.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o identity identity.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ndw-ui-test: dw_ui_test.o form.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o dw-ui-test dw_ui_test.o form.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ncontainers.o: containers.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c containers.cc\n\ncontainers: containers.o ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o containers containers.o ../lout/liblout.a\n\nshapes.o: shapes.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c shapes.cc\n\nshapes: shapes.o ../dw/libDw-core.a ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o shapes shapes.o ../dw/libDw-core.a ../lout/liblout.a\n\ncookies.o: cookies.c\n\t$(COMPILE) $(LIBFLTK_CXXFLAGS) -DBINNAME='\"$(BINNAME)\"' -c cookies.c\n\ncookies: cookies.o ../dpip/libDpip.a ../dlib/libDlib.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o cookies cookies.o ../dpip/libDpip.a ../dlib/libDlib.a\n\nliang.o: liang.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c liang.cc\n\nliang: liang.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o liang liang.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\ntrie.o: trie.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c trie.cc\n\ntrie: trie.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o trie trie.o ../dw/libDw-widgets.a  ../dw/libDw-fltk.a  ../dw/libDw-core.a  ../lout/liblout.a\n\nnotsosimplevector.o: notsosimplevector.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c notsosimplevector.cc\n\nnotsosimplevector: notsosimplevector.o ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o notsosimplevector notsosimplevector.o ../lout/liblout.a\n\nunicode_test.o: unicode_test.cc\n\t$(CXXCOMPILE) $(LIBFLTK_CXXFLAGS) -c unicode_test.cc\n\nunicode-test: unicode_test.o ../lout/liblout.a\n\t$(CXXCOMPILE) $(LIBFLTK_LDFLAGS) -o unicode-test unicode_test.o ../lout/liblout.a\n\nclean:\n\trm -f *.o\n\trm -f dw-anchors-test dw-example dw-find-test dw-float-test dw-links dw-links2 dw-image-background dw-images-simple dw-images-scaled dw-images-scaled2 dw-lists dw-simple-container-test dw-table-aligned dw-table dw-border-test dw-imgbuf-mem-test identity dw-ui-test dw-resource-test containers shapes cookies liang trie notsosimplevector unicode-test\n\ninstall:\nuninstall:\n"
  },
  {
    "path": "test/anchors.html",
    "content": "<p lang=\"de\"><a href=\"#unten\">Nach\nunten</a>. Grundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung.\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung.\n<span id=\"unten\"><b>Hier ist unten.</b> (Nicht ganz.)</span> Grundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung.\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung.\n</p>\n"
  },
  {
    "path": "test/containers.cc",
    "content": "#include \"../lout/object.hh\"\n#include \"../lout/container.hh\"\n\nusing namespace lout::object;\nusing namespace lout::container::typed;\n\nclass ReverseComparator: public Comparator\n{\nprivate:\n   Comparator *reversed;\n\npublic:\n   ReverseComparator (Comparator *reversed) { this->reversed = reversed; }\n   int compare(Object *o1, Object *o2) { return - reversed->compare (o1, o2); }\n};\n\nvoid testHashSet ()\n{\n   puts (\"--- testHashSet ---\");\n\n   HashSet<String> h(true);\n\n   h.put (new String (\"one\"));\n   h.put (new String (\"two\"));\n   h.put (new String (\"three\"));\n\n   puts (h.toString());\n}\n\nvoid testHashTable ()\n{\n   puts (\"--- testHashTable ---\");\n\n   HashTable<String, Integer> h(true, true);\n\n   h.put (new String (\"one\"), new Integer (1));\n   h.put (new String (\"two\"), new Integer (2));\n   h.put (new String (\"three\"), new Integer (3));\n\n   puts (h.toString());\n\n   h.put (new String (\"one\"), new Integer (4));\n   h.put (new String (\"two\"), new Integer (5));\n   h.put (new String (\"three\"), new Integer (6));\n\n   puts (h.toString());\n}\n\nvoid testVector1 ()\n{\n   ReverseComparator reverse (&standardComparator);\n\n   puts (\"--- testVector (1) ---\");\n\n   Vector<String> v (true, 1);\n\n   v.put (new String (\"one\"));\n   v.put (new String (\"two\"));\n   v.put (new String (\"three\"));\n   puts (v.toString());\n\n   v.sort (&reverse);\n   puts (v.toString());\n\n   v.sort ();\n   puts (v.toString());\n}\n\nvoid testVector2 ()\n{\n   puts (\"--- testVector (2) ---\");\n\n   Vector<String> v (true, 1);\n\n   v.insertSorted (new String (\"one\"));\n   puts (v.toString());\n\n   v.insertSorted (new String (\"two\"));\n   puts (v.toString());\n\n   v.insertSorted (new String (\"three\"));\n   puts (v.toString());\n\n   v.insertSorted (new String (\"five\"));\n   puts (v.toString());\n\n   v.insertSorted (new String (\"six\"));\n   puts (v.toString());\n\n   v.insertSorted (new String (\"four\"));\n   puts (v.toString());\n\n   for (int b = 0; b < 2; b++) {\n      bool mustExist = b;\n      printf (\"mustExist = %s\\n\", mustExist ? \"true\" : \"false\");\n\n      String k (\"alpha\");\n      printf (\"   '%s' -> %d\\n\", k.chars(), v.bsearch (&k, mustExist));\n\n      for (Iterator<String> it = v.iterator(); it.hasNext(); ) {\n         String *k1 = it.getNext();\n         printf (\"   '%s' -> %d\\n\", k1->chars(), v.bsearch (k1, mustExist));\n\n         char buf[64];\n         strcpy (buf, k1->chars());\n         strcat (buf, \"-var\");\n         String k2 (buf);\n         printf (\"   '%s' -> %d\\n\", k2.chars(), v.bsearch (&k2, mustExist));\n      }\n   }\n}\n\nvoid testVector3 ()\n{\n   // Regression test: resulted once incorrently (0, 2, 3), should\n   // result in (1, 2, 3).\n\n   puts (\"--- testVector (3) ---\");\n\n   Vector<String> v (true, 1);\n   String k (\"omega\");\n\n   v.put (new String (\"alpha\"));\n   printf (\"   -> %d\\n\", v.bsearch (&k, false));\n   v.put (new String (\"beta\"));\n   printf (\"   -> %d\\n\", v.bsearch (&k, false));\n   v.put (new String (\"gamma\"));\n   printf (\"   -> %d\\n\", v.bsearch (&k, false));\n}\n\nvoid testStackAsQueue ()\n{\n   puts (\"--- testStackAsQueue ---\");\n\n   Stack<Integer> s (true);\n\n   for (int i = 1; i <= 10; i++)\n      s.pushUnder (new Integer (i));\n\n   while (s.size () > 0) {\n      Integer *i = s.getTop ();\n      printf (\"%d\\n\", i->getValue ());\n      s.pop ();\n   }\n}\n\nint main (int argc, char *argv[])\n{\n   testHashSet ();\n   testHashTable ();\n   testVector1 ();\n   testVector2 ();\n   testVector3 ();\n   testStackAsQueue ();\n\n   return 0;\n}\n"
  },
  {
    "path": "test/cookies.c",
    "content": "/*\n * Dillo cookies test\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n/*\n * This has a big blob of the current src/IO/dpi.c in it.\n * I hope there's a better way.\n */\n\n#include <stdlib.h> /* malloc, etc. */\n#include <unistd.h> /* read, etc. */\n#include <stdio.h>\n#include <stdarg.h>  /* va_list */\n#include <string.h> /* strchr */\n#include <errno.h>\n#include <ctype.h>\n#include <time.h>\n/* net */\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <arpa/inet.h>\n#include <netinet/in.h>\n\n\n#define _MSG(...)\n\n#define MSG_INNARDS(prefix, ...)                   \\\n   D_STMT_START {                                  \\\n      printf(prefix __VA_ARGS__);               \\\n      fflush (stdout);                          \\\n   } D_STMT_END\n\n#define MSG(...) MSG_INNARDS(\"\", __VA_ARGS__)\n#define MSG_WARN(...) MSG_INNARDS(\"** WARNING **: \", __VA_ARGS__)\n#define MSG_ERR(...) MSG_INNARDS(\"** ERROR **: \", __VA_ARGS__)\n\n\n#include \"../dlib/dlib.h\"\n#include \"../dpip/dpip.h\"\n\nstatic uint_t failed = 0;\nstatic uint_t passed = 0;\n\nstatic char SharedKey[32];\n\n/*\n * Read all the available data from a filedescriptor.\n * This is intended for short answers, i.e. when we know the server\n * will write it all before being preempted. For answers that may come\n * as an stream with delays, non-blocking is better.\n * Return value: read data, or NULL on error and no data.\n */\nstatic char *Dpi_blocking_read(int fd)\n{\n   int st;\n   const int buf_sz = 8*1024;\n   char buf[buf_sz], *msg = NULL;\n   Dstr *dstr = dStr_sized_new(buf_sz);\n\n   do {\n      st = read(fd, buf, buf_sz);\n      if (st < 0) {\n         if (errno == EINTR) {\n            continue;\n         } else {\n            MSG_ERR(\"[Dpi_blocking_read] %s\\n\", dStrerror(errno));\n            break;\n         }\n      } else if (st > 0) {\n         dStr_append_l(dstr, buf, st);\n      }\n   } while (st == buf_sz);\n\n   msg = (dstr->len > 0) ? dstr->str : NULL;\n   dStr_free(dstr, (dstr->len > 0) ? FALSE : TRUE);\n   return msg;\n}\n\nstatic void Dpi_close_fd(int fd)\n{\n   int st;\n\n   dReturn_if (fd < 0);\n   do\n      st = close(fd);\n   while (st < 0 && errno == EINTR);\n}\n\nstatic int Dpi_make_socket_fd()\n{\n   int fd, ret = -1;\n\n   if ((fd = socket(AF_INET, SOCK_STREAM, 0)) != -1) {\n      ret = fd;\n   }\n   return ret;\n}\n\n/*\n * Read dpid's communication keys from its saved file.\n * Return value: 1 on success, -1 on error.\n */\nstatic int Dpi_read_comm_keys(int *port)\n{\n   FILE *In;\n   char *fname, *rcline = NULL, *tail;\n   int i, ret = -1;\n\n   fname = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/dpid_comm_keys\", NULL);\n   if ((In = fopen(fname, \"r\")) == NULL) {\n      MSG_ERR(\"[Dpi_read_comm_keys] %s\\n\", dStrerror(errno));\n   } else if ((rcline = dGetline(In)) == NULL) {\n      MSG_ERR(\"[Dpi_read_comm_keys] empty file: %s\\n\", fname);\n   } else {\n      *port = strtol(rcline, &tail, 10);\n      for (i = 0; *tail && isxdigit(tail[i+1]); ++i)\n         SharedKey[i] = tail[i+1];\n      SharedKey[i] = 0;\n      ret = 1;\n   }\n   if (In)\n      fclose(In);\n   dFree(rcline);\n   dFree(fname);\n\n   return ret;\n}\n\nstatic int Dpi_check_dpid_ids()\n{\n   struct sockaddr_in sin;\n   const socklen_t sin_sz = sizeof(sin);\n   int sock_fd, dpid_port, ret = -1;\n\n   /* socket connection test */\n   memset(&sin, 0, sizeof(sin));\n   sin.sin_family = AF_INET;\n   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n\n   if (Dpi_read_comm_keys(&dpid_port) != -1) {\n      sin.sin_port = htons(dpid_port);\n      if ((sock_fd = Dpi_make_socket_fd()) == -1) {\n         MSG(\"Dpi_check_dpid_ids: sock_fd=%d %s\\n\", sock_fd, dStrerror(errno));\n      } else if (connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) {\n         MSG(\"Dpi_check_dpid_ids: %s\\n\", dStrerror(errno));\n      } else {\n         Dpi_close_fd(sock_fd);\n         ret = 1;\n      }\n   }\n   return ret;\n}\n\nstatic int Dpi_blocking_write(int fd, const char *msg, int msg_len)\n{\n   int st, sent = 0;\n\n   while (sent < msg_len) {\n      st = write(fd, msg + sent, msg_len - sent);\n      if (st < 0) {\n         if (errno == EINTR) {\n            continue;\n         } else {\n            MSG_ERR(\"[Dpi_blocking_write] %s\\n\", dStrerror(errno));\n            break;\n         }\n      }\n      sent += st;\n   }\n\n   return (sent == msg_len) ? 1 : -1;\n}\n\n/*\n * Start dpid.\n * Return: 0 starting now, 1 Error.\n */\nstatic int Dpi_start_dpid(void)\n{\n   pid_t pid;\n   int st_pipe[2], ret = 1;\n   char *answer;\n\n   /* create a pipe to track our child's status */\n   if (pipe(st_pipe))\n      return 1;\n\n   pid = fork();\n   if (pid == 0) {\n      /* This is the child process.  Execute the command. */\n      char *path1 = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/dpid-plus\", NULL);\n      Dpi_close_fd(st_pipe[0]);\n      if (execl(path1, \"dpid-plus\", (char*)NULL) == -1) {\n         dFree(path1);\n         if (execlp(\"dpid-plus\", \"dpid-plus\", (char*)NULL) == -1) {\n            MSG(\"Dpi_start_dpid (child): %s\\n\", dStrerror(errno));\n            if (Dpi_blocking_write(st_pipe[1], \"ERROR\", 5) == -1) {\n               MSG(\"Dpi_start_dpid (child): can't write to pipe.\\n\");\n            }\n            Dpi_close_fd(st_pipe[1]);\n            _exit (EXIT_FAILURE);\n         }\n      }\n   } else if (pid < 0) {\n      /* The fork failed.  Report failure.  */\n      MSG(\"Dpi_start_dpid: %s\\n\", dStrerror(errno));\n      /* close the unused pipe */\n      Dpi_close_fd(st_pipe[0]);\n      Dpi_close_fd(st_pipe[1]);\n\n   } else {\n      /* This is the parent process, check our child status... */\n      Dpi_close_fd(st_pipe[1]);\n      if ((answer = Dpi_blocking_read(st_pipe[0])) != NULL) {\n         MSG(\"Dpi_start_dpid: can't start dpid-plus\\n\");\n         dFree(answer);\n      } else {\n         ret = 0;\n      }\n      Dpi_close_fd(st_pipe[0]);\n   }\n\n   return ret;\n}\n\n/*\n * Confirm that the dpid is running. If not, start it.\n * Return: 0 running OK, 1 starting (EAGAIN), 2 Error.\n */\nstatic int Dpi_check_dpid(int num_tries)\n{\n   static int starting = 0;\n   int check_st = 1, ret = 2;\n\n   check_st = Dpi_check_dpid_ids();\n   _MSG(\"Dpi_check_dpid: check_st=%d\\n\", check_st);\n\n   if (check_st == 1) {\n      /* connection test with dpi server passed */\n      starting = 0;\n      ret = 0;\n   } else {\n      if (!starting) {\n         /* start dpid */\n         if (Dpi_start_dpid() == 0) {\n            starting = 1;\n            ret = 1;\n         }\n      } else if (++starting < num_tries) {\n         /* starting */\n         ret = 1;\n      } else {\n         /* we waited too much, report an error... */\n         starting = 0;\n      }\n   }\n\n   _MSG(\"Dpi_check_dpid:: %s\\n\",\n        (ret == 0) ? \"OK\" : (ret == 1 ? \"EAGAIN\" : \"ERROR\"));\n   return ret;\n}\n\n\nstatic int Dpi_blocking_start_dpid(void)\n{\n   int cst, try = 0,\n       n_tries = 12; /* 3 seconds */\n\n   /* test the dpid, and wait a bit for it to start if necessary */\n   while ((cst = Dpi_check_dpid(n_tries)) == 1) {\n      MSG(\"Dpi_blocking_start_dpid: try %d\\n\", ++try);\n      usleep(250000); /* 1/4 sec */\n   }\n   return cst;\n}\n\n\n/*\n * Return the dpi server's port number, or -1 on error.\n * (A query is sent to dpid and then its answer parsed)\n * note: as the available servers and/or the dpi socket directory can\n *       change at any time, we'll ask each time. If someday we find\n *       that connecting each time significantly degrades performance,\n *       an optimized approach can be tried.\n */\nstatic int Dpi_get_server_port(const char *server_name)\n{\n   int sock_fd = -1, dpi_port = -1;\n   int dpid_port, ok = 0;\n   struct sockaddr_in sin;\n   char *cmd, *request, *rply = NULL, *port_str;\n   socklen_t sin_sz;\n\n   dReturn_val_if_fail (server_name != NULL, dpi_port);\n   _MSG(\"Dpi_get_server_port:: server_name = [%s]\\n\", server_name);\n\n   /* Read dpid's port from saved file */\n   if (Dpi_read_comm_keys(&dpid_port) != -1) {\n      ok = 1;\n   }\n   if (ok) {\n      /* Connect a socket with dpid */\n      ok = 0;\n      sin_sz = sizeof(sin);\n      memset(&sin, 0, sizeof(sin));\n      sin.sin_family = AF_INET;\n      sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n      sin.sin_port = htons(dpid_port);\n      if ((sock_fd = Dpi_make_socket_fd()) == -1 ||\n          connect(sock_fd, (struct sockaddr *)&sin, sin_sz) == -1) {\n         MSG(\"Dpi_get_server_port: %s\\n\", dStrerror(errno));\n      } else {\n         ok = 1;\n      }\n   }\n   if (ok) {\n      /* ask dpid to check the dpi and send its port number back */\n      ok = 0;\n      request = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"check_server\", server_name);\n      _MSG(\"[%s]\\n\", request);\n\n      if (Dpi_blocking_write(sock_fd, request, strlen(request)) == -1) {\n         MSG(\"Dpi_get_server_port: %s\\n\", dStrerror(errno));\n      } else {\n         ok = 1;\n      }\n      dFree(request);\n   }\n   if (ok) {\n      /* Get the reply */\n      ok = 0;\n      if ((rply = Dpi_blocking_read(sock_fd)) == NULL) {\n         MSG(\"Dpi_get_server_port: can't read server port from dpid.\\n\");\n      } else {\n         ok = 1;\n      }\n   }\n   if (ok) {\n      /* Parse reply */\n      ok = 0;\n      cmd = a_Dpip_get_attr(rply, \"cmd\");\n      if (strcmp(cmd, \"send_data\") == 0) {\n         port_str = a_Dpip_get_attr(rply, \"msg\");\n         _MSG(\"Dpi_get_server_port: rply=%s\\n\", rply);\n         _MSG(\"Dpi_get_server_port: port_str=%s\\n\", port_str);\n         dpi_port = strtol(port_str, NULL, 10);\n         dFree(port_str);\n         ok = 1;\n      }\n      dFree(cmd);\n   }\n   dFree(rply);\n   Dpi_close_fd(sock_fd);\n\n   return ok ? dpi_port : -1;\n}\n\n\nstatic int Dpi_connect_socket(const char *server_name)\n{\n   struct sockaddr_in sin;\n   int sock_fd, dpi_port, ret = -1;\n   char *cmd = NULL;\n\n   /* Query dpid for the port number for this server */\n   if ((dpi_port = Dpi_get_server_port(server_name)) == -1) {\n      _MSG(\"Dpi_connect_socket:: can't get port number for %s\\n\", server_name);\n      return -1;\n   }\n   _MSG(\"Dpi_connect_socket: server=%s port=%d\\n\", server_name, dpi_port);\n\n   /* connect with this server's socket */\n   memset(&sin, 0, sizeof(sin));\n   sin.sin_family = AF_INET;\n   sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);\n   sin.sin_port = htons(dpi_port);\n\n   if ((sock_fd = Dpi_make_socket_fd()) == -1) {\n      perror(\"[dpi::socket]\");\n   } else if (connect(sock_fd, (void*)&sin, sizeof(sin)) == -1) {\n      MSG(\"[dpi::connect] errno:%d %s\\n\", errno, dStrerror(errno));\n\n   /* send authentication Key (the server closes sock_fd on auth error) */\n   } else if (!(cmd = a_Dpip_build_cmd(\"cmd=%s msg=%s\", \"auth\", SharedKey))) {\n      MSG_ERR(\"[Dpi_connect_socket] Can't make auth message.\\n\");\n   } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) {\n      MSG_ERR(\"[Dpi_connect_socket] Can't send auth message.\\n\");\n   } else {\n      ret = sock_fd;\n   }\n   dFree(cmd);\n   if (sock_fd != -1 && ret == -1) /* can't send cmd? */\n      Dpi_close_fd(sock_fd);\n\n   return ret;\n}\n\n\nchar *a_Dpi_send_blocking_cmd(const char *server_name, const char *cmd)\n{\n   int cst, sock_fd;\n   char *ret = NULL;\n\n   /* test the dpid, and wait a bit for it to start if necessary */\n   if ((cst = Dpi_blocking_start_dpid()) != 0) {\n      return ret;\n   }\n\n   if ((sock_fd = Dpi_connect_socket(server_name)) == -1) {\n      MSG_ERR(\"[a_Dpi_send_blocking_cmd] Can't connect to server.\\n\");\n   } else if (Dpi_blocking_write(sock_fd, cmd, strlen(cmd)) == -1) {\n      MSG_ERR(\"[a_Dpi_send_blocking_cmd] Can't send message.\\n\");\n   } else if ((ret = Dpi_blocking_read(sock_fd)) == NULL) {\n      MSG_ERR(\"[a_Dpi_send_blocking_cmd] Can't read message.\\n\");\n   }\n   Dpi_close_fd(sock_fd);\n\n   return ret;\n}\n\n\n\nvoid a_Cookies_set(const char *cookie, const char *host, const char *path,\n                   const char *date)\n{\n   char *cmd, *dpip_tag;\n\n   if (date)\n      cmd = a_Dpip_build_cmd(\"cmd=%s cookie=%s host=%s path=%s date=%s\",\n                             \"set_cookie\", cookie,\n                             host, path, date);\n   else\n      cmd = a_Dpip_build_cmd(\"cmd=%s cookie=%s host=%s path=%s\",\n                             \"set_cookie\", cookie,\n                             host, path);\n\n   dpip_tag = a_Dpi_send_blocking_cmd(\"cookies\", cmd);\n   _MSG(\"a_Cookies_set: dpip_tag = {%s}\\n\", dpip_tag);\n   dFree(dpip_tag);\n   dFree(cmd);\n}\n\n\nchar *a_Cookies_get_query(const char *scheme, const char *host,\n                          const char *path)\n{\n   char *cmd, *dpip_tag, *query;\n\n   cmd = a_Dpip_build_cmd(\"cmd=%s scheme=%s host=%s path=%s\",\n                          \"get_cookie\", scheme,\n                          host, path);\n\n   /* Get the answer from cookies.dpi */\n   _MSG(\"cookies.c: a_Dpi_send_blocking_cmd cmd = {%s}\\n\", cmd);\n   dpip_tag = a_Dpi_send_blocking_cmd(\"cookies\", cmd);\n   _MSG(\"cookies.c: after a_Dpi_send_blocking_cmd resp={%s}\\n\", dpip_tag);\n   dFree(cmd);\n\n   if (dpip_tag != NULL) {\n      query = a_Dpip_get_attr(dpip_tag, \"cookie\");\n      dFree(dpip_tag);\n   } else {\n      query = dStrdup(\"\");\n   }\n\n   return query;\n}\n\nstatic void expect(int lineno, const char *exp_reply,\n                      const char *scheme, const char *host, const char *path)\n{\n   char *reply = a_Cookies_get_query(scheme, host, path);\n\n   if (strcmp(reply, exp_reply)) {\n      MSG(\"line %d: EXPECTED: %s GOT: %s\\n\", lineno, exp_reply, reply);\n      failed++;\n   } else {\n      passed++;\n   }\n}\n\nstatic void toomany()\n{\n   a_Cookies_set(\"1=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"2=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"3=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"4=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"5=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"6=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"7=1\", \"toomany.com\", \"/path/\", NULL);\n   a_Cookies_set(\"8=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"9=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"10=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"11=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"12=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"13=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"14=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"15=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"16=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"17=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"18=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"19=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"20=1\", \"toomany.com\", \"/\", NULL);\n   a_Cookies_set(\"21=1\", \"toomany.com\", \"/\", NULL);\n   /* 1 was oldest and discarded */\n   expect(__LINE__, \"Cookie: 7=1; 2=1; 3=1; 4=1; 5=1; 6=1; 8=1; 9=1; 10=1; \"\n                    \"11=1; 12=1; 13=1; 14=1; 15=1; 16=1; 17=1; 18=1; 19=1; \"\n                    \"20=1; 21=1\\r\\n\", \"http\", \"toomany.com\", \"/path/\");\n   sleep(1);\n   /* touch all of them except #7 (path matching) */\n   expect(__LINE__, \"Cookie: 2=1; 3=1; 4=1; 5=1; 6=1; 8=1; 9=1; 10=1; \"\n                    \"11=1; 12=1; 13=1; 14=1; 15=1; 16=1; 17=1; 18=1; 19=1; \"\n                    \"20=1; 21=1\\r\\n\", \"http\", \"toomany.com\", \"/\");\n   a_Cookies_set(\"22=1\", \"toomany.com\", \"/\", NULL);\n   /* 7 was oldest and discarded */\n   expect(__LINE__, \"Cookie: 2=1; 3=1; 4=1; 5=1; 6=1; 8=1; 9=1; 10=1; \"\n                    \"11=1; 12=1; 13=1; 14=1; 15=1; 16=1; 17=1; 18=1; 19=1; \"\n                    \"20=1; 21=1; 22=1\\r\\n\", \"http\", \"toomany.com\", \"/path/\");\n}\n\nstatic void maxage()\n{\n   time_t t = time(NULL)+1000;\n   char *server_date = dStrdup(ctime(&t));\n\n   a_Cookies_set(\"name=val; max-age=0\", \"maxage0.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"maxage0.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=-0\", \"maxage-0.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"maxage-0.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=100\", \"maxage100.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"maxage100.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=-100\", \"maxage-100.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"maxage-100.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=2000000000\", \"maxage2bil.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"maxage2bil.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=3000000000\", \"maxage3bil.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"maxage3bil.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=7000000000\", \"maxage7bil.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"maxage7bil.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=-2000000000\", \"maxage-2bil.com\", \"/\",NULL);\n   expect(__LINE__, \"\", \"http\", \"maxage-2bil.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=-3000000000\", \"maxage-3bil.com\", \"/\",NULL);\n   expect(__LINE__, \"\", \"http\", \"maxage-3bil.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=-7000000000\", \"maxage-7bil.com\", \"/\",NULL);\n   expect(__LINE__, \"\", \"http\", \"maxage-7bil.com\", \"/\");\n\n   /* just having a server date shouldn't matter */\n\n   a_Cookies_set(\"name=val; max-age=0\", \"maxage0s.com\", \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"maxage0s.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=100\", \"maxage100s.com\", \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"maxage100s.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=-100\", \"maxage-100s.com\", \"/\",server_date);\n   expect(__LINE__, \"\", \"http\", \"maxage-100s.com\", \"/\");\n\n   /* MAX-AGE and EXPIRES */\n   a_Cookies_set(\"name=val; max-age=90; expires=Wed Jan 20 01:26:32 2010\",\n                 \"maxagelater.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"maxagelater.com\", \"/\");\n\n   a_Cookies_set(\"name=val; max-age=90; expires=Wed Jan 20 01:26:32 2010\",\n                 \"maxagelaters.com\", \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"maxagelaters.com\", \"/\");\n\n   dFree(server_date);\n}\n\nstatic void expires_server_ahead()\n{\n   char *string;\n   time_t t = time(NULL)+1000;\n   char *server_date = dStrdup(ctime(&t));\n   time_t expt = t + 1000;\n   char *exp_date = dStrdup(ctime(&expt));\n\n   string = dStrconcat(\"name=val; expires=\", exp_date, NULL);\n   a_Cookies_set(string, \"e2000s1000.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"e2000s1000.com\", \"/\");\n\n   a_Cookies_set(string, \"e2000s1000s.com\", \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"e2000s1000s.com\", \"/\");\n\n   expt = t - 500; /* past for the server, future for us */\n   dFree(exp_date);\n   exp_date = dStrdup(ctime(&expt));\n\n   string = dStrconcat(\"name=val; expires=\", exp_date, NULL);\n   a_Cookies_set(string, \"e500s1000.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"e500s1000.com\", \"/\");\n\n   a_Cookies_set(string, \"e500s1000s.com\", \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"e500s1000s.com\", \"/\");\n\n   expt = t; /* expire at future-for-us server date */\n   dFree(exp_date);\n   exp_date = dStrdup(ctime(&expt));\n\n   string = dStrconcat(\"name=val; expires=\", exp_date, NULL);\n   a_Cookies_set(string, \"e1000s1000.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"e1000s1000.com\", \"/\");\n\n   a_Cookies_set(string, \"e1000s1000s.com\", \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"e1000s1000s.com\", \"/\");\n\n   expt = time(NULL); /* now */\n   dFree(exp_date);\n   exp_date = dStrdup(ctime(&expt));\n\n   string = dStrconcat(\"name=val; expires=\", exp_date, NULL);\n   a_Cookies_set(string, \"e0s1000.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"e0s1000.com\", \"/\");\n\n   a_Cookies_set(string, \"e0s1000s.com\", \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"e0s1000s.com\", \"/\");\n\n   dFree(exp_date);\n   dFree(server_date);\n}\n\nstatic void expires_server_behind()\n{\n   char *string;\n   time_t t = time(NULL)-1000;\n   char *server_date = dStrdup(ctime(&t));\n\n   time_t expt = t + 1000;\n   char *exp_date = dStrdup(ctime(&expt));\n\n   string = dStrconcat(\"name=val; expires=\", exp_date, NULL);\n   a_Cookies_set(string, \"e0s-1000.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"e0s-1000.com\", \"/\");\n\n   a_Cookies_set(string, \"e0s-1000s.com\", \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"e0s-1000s.com\",\"/\");\n\n   expt = t + 500; /* future for the server, past for us */\n   dFree(exp_date);\n   exp_date = dStrdup(ctime(&expt));\n\n   string = dStrconcat(\"name=val; expires=\", exp_date, NULL);\n   a_Cookies_set(string, \"e-500s-1000.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"e-500s-1000.com\", \"/\");\n\n   a_Cookies_set(string, \"e-500s-1000s.com\", \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"e-500s-1000s.com\", \"/\");\n\n   expt = t; /* expire at past-for-us server date */\n   dFree(exp_date);\n   exp_date = dStrdup(ctime(&expt));\n\n   string = dStrconcat(\"name=val; expires=\", exp_date, NULL);\n   a_Cookies_set(string, \"e-1000s-1000.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"e-1000s-1000.com\", \"/\");\n\n   a_Cookies_set(string, \"e-1000s-1000s.com\", \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"e-1000s-1000s.com\", \"/\");\n\n   dFree(server_date);\n   dFree(exp_date);\n}\n\nstatic void expires_extremes()\n{\n   time_t t;\n   char *server_date;\n\n   a_Cookies_set(\"name=val; expires=Fri Dec 13 20:45:52 1801\", \"expmin.com\",\n                 \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"expmin.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Fri Dec 13 20:45:52 1901\", \"expmin2.com\",\n                 \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"expmin2.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Wed Dec 31 23:59:59 1969\", \"expneg.com\",\n                 \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"expneg.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Thu, 01-January-70 00:00:00 GMT\",\n                 \"expepoch.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"expepoch.com\", \"/\");\n\n   /* TODO: revisit these tests in a few decades */\n   a_Cookies_set(\"name=val; expires=Tue Jan 19 03:14:07 2038\", \"expmax.com\",\n                 \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"expmax.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Sun January  1 00:00:00 2040\",\n                 \"pastmax.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"pastmax.com\", \"/\");\n\n   t = time(NULL)+1000;\n   server_date = dStrdup(ctime(&t));\n\n   a_Cookies_set(\"name=val; expires=Fri Dec 13 20:45:52 1901\", \"expmina.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"expmina.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Wed Dec 31 23:59:59 1969\", \"expnega.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"expnega.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Thu Jan  1 00:00:00 1970\", \"expepocha.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"expepocha.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Tue Jan 19 03:14:07 2038\", \"expmaxa.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"expmaxa.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Thu, 01-Jan-40 00:00:00 GMT\",\n                 \"pastmaxa.com\", \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"pastmaxa.com\", \"/\");\n\n   t = time(NULL)-1000;\n   dFree(server_date);\n   server_date = dStrdup(ctime(&t));\n\n   a_Cookies_set(\"name=val; expires=Fri Dec 13 20:45:52 1901\", \"expminb.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"expminb.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Wed Dec 31 23:59:59 1969\", \"expnegb.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"expnegb.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Thu Jan  1 00:00:00 1970\", \"expepochb.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"\", \"http\", \"expepochb.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Tue Jan 19 03:14:07 2038\", \"expmaxb.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"expmaxb.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Sun Jan  1 00:00:00 2040\", \"pastmaxb.com\",\n                 \"/\", server_date);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"pastmaxb.com\", \"/\");\n\n   dFree(server_date);\n}\n\n/*\n * On 11 Aug 2009, Dan Winship posted to the http-state list with a bunch of\n * date formats he'd gathered. Let's work from that. I'll include his comments\n * below in double quotes.\n */\nstatic void expires_date_formats()\n{\n   /* \"Revised Netscape spec format\" */\n   a_Cookies_set(\"name=val; expires=Mon, 10-Dec-2037 17:02:24 GMT\",\n                 \"format1.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format1.com\", \"/\");\n\n   /* \"rfc1123-date\" */\n   a_Cookies_set(\"name=val; expires=Wed, 09 Dec 2037 16:27:23 GMT\",\n                 \"format2.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format2.com\", \"/\");\n\n   /* \"4-digit-year version of Netscape spec example (see below).\n    * Seems to only come from sites using PHP, but it's not PHP\n    * itself; maybe some framework?\"\n    */\n   a_Cookies_set(\"name=val; expires=Thursday, 01-Jan-2036 00:00:00 GMT\",\n                 \"format3.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format3.com\", \"/\");\n\n   /* \"The not-quite-asctime format used by Amazon.\" */\n   a_Cookies_set(\"name=val; expires=Mon Dec 10 16:32:30 2037 GMT\",\n                 \"format4.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format4.com\", \"/\");\n\n   /* \"The syntax used by the example text in the Netscape spec,\n    * although the actual grammar uses abbreviated weekday names\"\n    */\n   a_Cookies_set(\"name=val; expires=Wednesday, 01-Jan-37 00:00:00 GMT\",\n                 \"format5.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format5.com\", \"/\");\n \n   /* \"Original Netscape spec\" */\n   a_Cookies_set(\"name=val; expires=Mon, 10-Dec-37 20:35:03 GMT\",\n                 \"format6.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format6.com\", \"/\");\n\n   /* \"If this had '01 Jan' it would be an rfc1123-date. This *is* a\n    * legitimate rfc822 date, though not an rfc2822 date because 'GMT'\n    * is deprecated in favor of '+0000' there.\"\n    */\n   a_Cookies_set(\"name=val; expires=Wed, 1 Jan 2035 00:00:00 GMT\",\n                 \"format7.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format7.com\", \"/\");\n\n   /* \"Would match the 'weird php' syntax above if it was '08-Dec'\" */\n   a_Cookies_set(\"name=val; expires=Saturday, 8-Dec-2035 21:24:09 GMT\",\n                 \"format8.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format8.com\", \"/\");\n\n   /* \"God only knows what they were thinking. This came from a hit-tracker\n    * site, and it's possible that it's just totally broken and no one parses\n    * it 'correctly'\"\n    */\n   a_Cookies_set(\"name=val; expires=Thu, 31 Dec 23:55:55 2037 GMT\",\n                 \"format9.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"format9.com\", \"/\");\n\n   /* \"Another kind of rfc822 / nearly-rfc1123 date, using superfluous\n    * whitespace.\"\n    */\n   a_Cookies_set(\"name=val; expires=Sun,  9 Dec 2036 13:42:05 GMT\",\n                 \"formata.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"formata.com\", \"/\");\n\n   /* \"Another kind of 'lets throw components together at random'. The\n    * site that this cookie came has apparently been fixed since then.\n    * (It uses the Netscape spec format now.)\"\n    */\n   a_Cookies_set(\"name=val; expires=Wed Dec 12 2037 08:44:07 GMT-0500 (EST)\",\n                 \"formatb.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"formatb.com\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=Sun, 1-Jan-2035 00:00:00 GMT\",\n                 \"formatc.com\", \"/\", NULL); \n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"formatc.com\", \"/\");\n\n   /* ...and the remaining handful that he encountered once or twice were\n    * far too broken to deserve our attention (e.g., times like \"13:57:2\").\n    */\n\n   /* Now here's what github was sending in 2015. */\n   a_Cookies_set(\"name=val; expires=Sat, 07 Jul 2035 21:41:24 -0000\",\n                 \"formatd.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"formatd.com\", \"/\");\n\n}\n\nstatic void path()\n{\n   a_Cookies_set(\"name=val; path=/\", \"p1.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p1.com\", \"/\");\n\n   a_Cookies_set(\"name=val; path=/dir1\", \"p2.com\", \"/dir2\", NULL);\n   expect(__LINE__, \"\", \"http\", \"p2.com\", \"/\");\n   expect(__LINE__, \"\", \"http\", \"p2.com\", \"/d\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p2.com\", \"/dir1\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p2.com\", \"/dir1/\");\n   expect(__LINE__, \"\", \"http\", \"p2.com\", \"/dir2\");\n   expect(__LINE__, \"\", \"http\", \"p2.com\", \"/dir11\");\n\n   a_Cookies_set(\"name=val; path=dir1\", \"p3.com\", \"/dir2\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p3.com\", \"/\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p3.com\", \"/dir1\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p3.com\", \"/dir2\");\n\n   a_Cookies_set(\"name=val; path=/dir1/\", \"p4.com\", \"/dir2\", NULL);\n   expect(__LINE__, \"\", \"http\", \"p4.com\", \"/\");\n   /* this next one strikes me as a bit odd, personally, but I suppose it's not\n    * a big deal */\n   expect(__LINE__, \"\", \"http\", \"p4.com\", \"/dir1\");\n   expect(__LINE__, \"\", \"http\", \"p4.com\", \"/dir11\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p4.com\", \"/dir1/\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p4.com\", \"/dir1/sub\");\n\n   a_Cookies_set(\"name=val\", \"p5.com\", \"/dir/subdir\", NULL);\n   expect(__LINE__, \"\", \"http\", \"p5.com\", \"/\");\n   expect(__LINE__, \"\", \"http\", \"p5.com\", \"/bir\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p5.com\", \"/dir\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p5.com\", \"/dir/\");\n\n   a_Cookies_set(\"name=val\", \"p6.com\", \"/dir/subdir/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"p6.com\", \"/dir/\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p6.com\", \"/dir/subdir\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"p6.com\", \"/dir/subdir/s\");\n}\n\nint Cookies_rc_check()\n{\n   const int line_maxlen = 4096;\n   FILE *stream;\n   char *filename;\n   char line[line_maxlen];\n   bool_t default_deny = TRUE;\n\n   /* Get a file pointer */\n   filename = dStrconcat(dGethomedir(), \"/.\" BINNAME \"/cookiesrc\", NULL);\n   stream = fopen(filename, \"r\");\n   dFree(filename);\n\n   if (!stream) {\n      MSG_ERR(\"Cannot run test; cannot open cookiesrc.\\n\");\n      return 1;\n   }\n\n   /* Get all lines in the file */\n   while (!feof(stream)) {\n      char *rc;\n\n      line[0] = '\\0';\n      rc = fgets(line, line_maxlen, stream);\n      if (!rc && ferror(stream)) {\n         MSG_ERR(\"Error while reading rule from cookiesrc: %s\\n\",\n             dStrerror(errno));\n         fclose(stream);\n         return 2;\n      }\n\n      /* Remove leading and trailing whitespaces */\n      dStrstrip(line);\n\n      if (line[0] != '\\0' && line[0] != '#') {\n         int domain_end, i = 0;\n         const char *rule;\n\n         /* Get the domain */\n         while (line[i] != '\\0' && !dIsspace(line[i]))\n            i++;\n         domain_end = i;\n\n         /* Skip past whitespace */\n         while (dIsspace(line[i]))\n            i++;\n         line[domain_end] = '\\0';\n\n         /* Get the rule */\n         rule = line + i;\n         while (line[i] != '\\0' && !dIsspace(line[i]))\n            i++;\n         line[i] = '\\0';\n\n         if (!dStrAsciiCasecmp(line, \"DEFAULT\")) {\n            if (!dStrAsciiCasecmp(rule, \"ACCEPT\") ||\n                !dStrAsciiCasecmp(rule, \"ACCEPT_SESSION\"))\n               default_deny = FALSE;\n         } else {\n            if (!dStrAsciiCasecmp(rule, \"DENY\"))\n               MSG_WARN(\"DENY rules in cookiesrc can interfere with test.\\n\");\n         }\n      }\n   }\n   fclose(stream);\n\n   if (default_deny) {\n      MSG_ERR(\"Cannot run test with cookiesrc default of deny.\\n\");\n      return 1;\n   } else {\n      return 0;\n   }\n}\n\nint main()\n{\n   if (Cookies_rc_check()) {\n      MSG(\"If you change cookiesrc, remember to stop the DPIs via dpidc.\\n\");\n      return 1;\n   }\n\n   a_Cookies_set(\"name=val\", \"ordinary.com\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"ordinary.com\", \"/\");\n\n   toomany();\n   maxage();\n   expires_server_ahead();\n   expires_server_behind();\n   expires_extremes();\n   expires_date_formats();\n\n   a_Cookies_set(\"name=val; expires=\\\"Sun Jan 10 00:00:00 2038\\\"\",\n                 \"quoted-date.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"quoted-date.org\", \"/\");\n\n   a_Cookies_set(\"name=val; expires=\\\"Sun Jan 11 00:00:00 1970\\\"\",\n                 \"quoted-pastdate.org\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"quoted-pastdate.org\", \"/\");\n\n   path();\n\n   /* LEADING/TRAILING DOTS AND A LITTLE PUBLIC SUFFIX */\n   a_Cookies_set(\"name=val; domain=co.il\", \"www.co.il\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"www.co.il\", \"/\");\n\n   a_Cookies_set(\"name=val; domain=.co.il\", \"www.co.il\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"www.co.il\", \"/\");\n\n   a_Cookies_set(\"name=val; domain=co.il.\", \"www.co.il.\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"www.co.il.\", \"/\");\n\n   a_Cookies_set(\"name=val; domain=.co.il.\", \"www.co.il.\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \".www.co.il.\", \"/\");\n\n   a_Cookies_set(\"name=val; domain=co.org\", \"www.co.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"www.co.org\", \"/\");\n\n   a_Cookies_set(\"name=val; domain=.cp.org\", \"www.cp.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"www.cp.org\", \"/\");\n\n\n   /* DOTDOMAIN */\n   a_Cookies_set(\"name=val; domain=.dotdomain.org\", \"dotdomain.org\", \"/\",\n                 NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"dotdomain.org\", \"/\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"www.dotdomain.org\", \"/\");\n\n   /* HOST_ONLY */\n   a_Cookies_set(\"name=val; domain=.hostonly.org\", \"hostonly.org\", \"/\", NULL);\n   a_Cookies_set(\"name2=val2\", \"hostonly.org\", \"/\", NULL);\n   a_Cookies_set(\"name3=val3; domain=hostonly.org\", \"hostonly.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val; name2=val2; name3=val3\\r\\n\", \"http\",\n          \"hostonly.org\", \"/\");\n   a_Cookies_set(\"name=new; domain=.hostonly.org\", \"hostonly.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=new; name2=val2; name3=val3\\r\\n\", \"http\",\n          \"hostonly.org\", \"/\");\n   a_Cookies_set(\"name2=new2\", \"hostonly.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=new; name2=new2; name3=val3\\r\\n\", \"http\",\n          \"hostonly.org\", \"/\");\n   a_Cookies_set(\"name3=new3; domain=hostonly.org\", \"hostonly.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=new; name2=new2; name3=new3\\r\\n\", \"http\",\n          \"hostonly.org\", \"/\");\n\n   /* SUBDOMAIN */\n   a_Cookies_set(\"name=val; domain=www.subdomain.com\", \"subdomain.com\", \"/\",\n                 NULL);\n   a_Cookies_set(\"name=val; domain=.www.subdomain.com\", \"subdomain.com\", \"/\",\n                 NULL);\n   expect(__LINE__, \"\", \"http\", \"subdomain.com\", \"/\");\n   expect(__LINE__, \"\", \"http\", \"www.subdomain.com\", \"/\");\n\n   /* SUPERDOMAIN(?) */\n   a_Cookies_set(\"name=val; domain=.supdomain.com\", \"www.supdomain.com\", \"/\",\n                 NULL);\n   a_Cookies_set(\"name2=val2; domain=supdomain.com\", \"www.supdomain.com\", \"/\",\n                 NULL);\n   expect(__LINE__, \"Cookie: name=val; name2=val2\\r\\n\", \"http\",\n          \"sub2.sub.supdomain.com\", \"/\");\n   expect(__LINE__, \"Cookie: name=val; name2=val2\\r\\n\", \"http\",\n          \"www.supdomain.com\", \"/\");\n   expect(__LINE__, \"Cookie: name=val; name2=val2\\r\\n\", \"http\",\n          \"supdomain.com\", \"/\");\n\n   /* UNRELATED */\n   a_Cookies_set(\"name=val; domain=another.com\", \"unrelated.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"another.com\", \"/\");\n   a_Cookies_set(\"name=val; domain=another.com\", \"a.org\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"another.com\", \"/\");\n   a_Cookies_set(\"name=val; domain=another.com\", \"badguys.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"another.com\", \"/\");\n   a_Cookies_set(\"name=val; domain=another.com\", \"more.badguys.com\", \"/\",\n                 NULL);\n   expect(__LINE__, \"\", \"http\", \"another.com\", \"/\");\n   a_Cookies_set(\"name=val; domain=another.com\", \"verybadguys.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"another.com\", \"/\");\n\n   a_Cookies_set(\"name=val; domain=similar.com\", \"imilar.com\", \"/\", NULL);\n   a_Cookies_set(\"name2=val2; domain=similar.com\", \"ssimilar.com\", \"/\", NULL);\n   a_Cookies_set(\"name3=val3; domain=.similar.com\", \"imilar.com\", \"/\", NULL);\n   a_Cookies_set(\"name4=val4; domain=.similar.com\", \"timilar.com\", \"/\", NULL);\n   a_Cookies_set(\"name4=val4; domain=.similar.com\", \"tiimilar.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"similar.com\", \"/\");\n\n   /* SECURE */\n   a_Cookies_set(\"name=val; secure\", \"secure.com\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"secure.com\", \"/\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"https\", \"secure.com\", \"/\");\n\n   /* HTTPONLY */\n   a_Cookies_set(\"name=val; HttpOnly\", \"httponly.net\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"httponly.net\", \"/\");\n\n   /* GIBBERISH ATTR IGNORED */\n   a_Cookies_set(\"name=val; ldkfals\", \"gibberish.net\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"gibberish.net\", \"/\");\n\n   /* WHITESPACE/DELIMITERS */\n   a_Cookies_set(\" name=val \", \"whitespace.net\", \"/\", NULL);\n   a_Cookies_set(\"name2=val2;\", \"whitespace.net\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val; name2=val2\\r\\n\", \"http\",\n          \"whitespace.net\", \"/\");\n\n   /* NAMELESS/VALUELESS */\n   a_Cookies_set(\"value\", \"nonameval.org\", \"/\", NULL);\n   a_Cookies_set(\"name=\", \"nonameval.org\", \"/\", NULL);\n   a_Cookies_set(\"name2= \", \"nonameval.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=; name2=\\r\\n\", \"http\", \"nonameval.org\", \"/\");\n   a_Cookies_set(\"=val2\", \"nonameval.org\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=; name2=\\r\\n\", \"http\", \"nonameval.org\", \"/\");\n\n\n   /* SOME IP ADDRS */\n\n   a_Cookies_set(\"name=val\", \"FEDC:BA98:7654:3210:FEDC:BA98:7654:3210\",\n                 \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\",\n          \"FEDC:BA98:7654:3210:FEDC:BA98:7654:3210\", \"/\");\n\n   a_Cookies_set(\"name=val\", \"::FFFF:129.144.52.38\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"::FFFF:129.144.52.38\",\n          \"/\");\n\n   a_Cookies_set(\"name=val\", \"127.0.0.1\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"127.0.0.1\", \"/\");\n\n   a_Cookies_set(\"name=val; domain=128.0.0.1\", \"128.0.0.1\", \"/\", NULL);\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"128.0.0.1\", \"/\");\n\n   a_Cookies_set(\"name=val; domain=130.0.0.1\", \"129.0.0.1\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"129.0.0.1\", \"/\");\n   expect(__LINE__, \"\", \"http\", \"130.0.0.1\", \"/\");\n\n   a_Cookies_set(\"name=val\", \"2.0.0.1\", \"/\", NULL);\n   a_Cookies_set(\"name=bad; domain=22.0.0.1\", \"2.0.0.1\", \"/\", NULL);\n   a_Cookies_set(\"name=bad; domain=.0.0.1\", \"2.0.0.1\", \"/\", NULL);\n   a_Cookies_set(\"name=bad; domain=not-ip.org\", \"2.0.0.1\", \"/\", NULL);\n   expect(__LINE__, \"\", \"http\", \"22.0.0.1\", \"/\");\n   expect(__LINE__, \"\", \"http\", \"not-ip.org\", \"/\");\n   expect(__LINE__, \"Cookie: name=val\\r\\n\", \"http\", \"2.0.0.1\", \"/\");\n\n#if 0\nHAD BEEN PLAYING AROUND WITH REAL PUBLIC SUFFIX\na_Cookies_set(\"name=val;domain=sub.sub.yokohama.jp\", \"sub.sub.yokohama.jp\", \"/\", NULL);\nMSG(\"sub sub yokohama should work: %s\\n\",\n    a_Cookies_get_query(\"http\", \"sub.sub.yokohama.jp\", \"/\"));\na_Cookies_set(\"name=val; domain=sub.tokyo.jp\", \"sub.sub.tokyo.jp\", \"/\", NULL);\nMSG(\"sub tokyo jp should fail: %s\\n\",\n    a_Cookies_get_query(\"http\", \"sub.sub.tokyo.jp\", \"/\"));\na_Cookies_set(\"name=val; domain=pref.chiba.jp\", \"sub.pref.chiba.jp\", \"/\", NULL);\nMSG(\"pref chiba jp should succeed: %s\\n\",\n    a_Cookies_get_query(\"http\", \"sub.pref.chiba.jp\", \"/\"));\na_Cookies_set(\"name=val; domain=org\", \"www.dillo.org\", \"/\", NULL);\na_Cookies_set(\"name=val; domain=org\", \"dillo.org\", \"/\", NULL);\na_Cookies_set(\"name=val; domain=org\", \".dillo.org\", \"/\", NULL);\na_Cookies_set(\"name=val; domain=org.\", \".dillo.org\", \"/\", NULL);\na_Cookies_set(\"name=val; domain=org.\", \".dillo.org.\", \"/\", NULL);\nMSG(\"org should fail: %s\\n\",\n    a_Cookies_get_query(\"http\", \"www.dillo.org\", \"/\"));\n#endif\n\n   MSG(\"TESTS: passed: %u failed: %u\\n\", passed, failed);\n\n   MSG(\"Now that everything is full of fake cookies, you should run \"\n       \"'dpidc stop', plus delete cookies.txt if necessary.\\n\");\n\n   return 0;\n}\n"
  },
  {
    "path": "test/dw_anchors_test.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <ctype.h>\n#include <FL/Fl_Window.H>\n#include <FL/Fl.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n\nusing namespace lout::container::typed;\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nstatic FltkPlatform *platform;\nstatic Layout *layout;\nstatic Fl_Window *window;\nstatic FltkViewport *viewport;\nstatic Style *topWidgetStyle, *widgetStyle, *wordStyle, *headingStyle;\nstatic Textblock *topTextblock = NULL;\nstatic int textblockNo = 0;\n\nstatic const char *numbers[10] = {\n   \"one\", \"two\", \"three\", \"four\", \"five\",\n   \"six\", \"seven\", \"eight\", \"nine\", \"ten\"\n};\n\nstatic void anchorCallback (Fl_Widget *widget, void *data)\n{\n   layout->setAnchor (numbers[(long)data]);\n}\n\nstatic void textTimeout (void *data)\n{\n   Textblock *oldTop = topTextblock;\n   topTextblock = new Textblock (false);\n\n   if (oldTop) {\n      oldTop->addLinebreak (wordStyle);\n      oldTop->addWidget (topTextblock, widgetStyle);\n   } else {\n      topTextblock->setStyle (topWidgetStyle);\n      layout->setWidget (topTextblock);\n   }\n\n   topTextblock->addAnchor (numbers[textblockNo], headingStyle);\n\n   char buf[16];\n   strcpy (buf, numbers[textblockNo]);\n   buf[0] = lout::misc::AsciiToupper (buf[0]);\n   topTextblock->addText (buf, headingStyle);\n   topTextblock->addParbreak (5, headingStyle);\n\n   for (int i = 0; i < 30; i++) {\n      strcpy (buf, numbers[textblockNo]);\n      if (i == 0)\n         buf[0] = lout::misc::AsciiToupper (buf[0]);\n      strcat (buf, i == 29 ? \".\" : \",\");\n\n      topTextblock->addText (buf, wordStyle);\n      topTextblock->addSpace (wordStyle);\n   }\n\n   topTextblock->flush ();\n\n   textblockNo++;\n   if (textblockNo < 10)\n      Fl::repeat_timeout (1, textTimeout, NULL);\n\n}\n\nint main(int argc, char **argv)\n{\n   char *buttonLabel[10];\n\n   platform = new FltkPlatform ();\n   layout = new Layout (platform);\n\n   window = new Fl_Window(250, 200, \"Dw Anchors Test\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   viewport = new FltkViewport (50, 0, 200, 200);\n   viewport->end();\n   layout->attachView (viewport);\n\n   for (int i = 0; i < 10; i++) {\n      char buf[16];\n      strcpy (buf, numbers[i]);\n      buf[0] = lout::misc::AsciiToupper (buf[0]);\n      buttonLabel[i] = strdup(buf);\n      Fl_Button *button = new Fl_Button(0, 20 * i, 50, 20, buttonLabel[i]);\n      button->callback (anchorCallback, (void*)(long)i);\n      button->when (FL_WHEN_RELEASE);\n   }\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n   styleAttrs.margin.setVal (5);\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n   topWidgetStyle = Style::create (&styleAttrs);\n\n   styleAttrs.margin.left = 20;\n   styleAttrs.margin.right = 0;\n   styleAttrs.backgroundColor = NULL;\n   widgetStyle = Style::create (&styleAttrs);\n\n   styleAttrs.margin.left = 0;\n   wordStyle = Style::create (&styleAttrs);\n\n   fontAttrs.size = 28;\n   fontAttrs.weight = 700;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n   headingStyle = Style::create (&styleAttrs);\n\n   Fl::add_timeout (0, textTimeout, NULL);\n\n   window->resizable(viewport);\n   window->show();\n\n   int errorCode = Fl::run();\n\n   topWidgetStyle->unref ();\n   widgetStyle->unref ();\n   wordStyle->unref ();\n   headingStyle->unref ();\n   for (int i = 0; i < 10; i++)\n      free(buttonLabel[i]);\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_border_test.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl_Window.H>\n#include <FL/Fl.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n#include \"../dw/listitem.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Border Test\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n   styleAttrs.borderWidth.setVal (2);\n   styleAttrs.setBorderColor (Color::create (layout, 0xffffff));\n   styleAttrs.setBorderStyle (BORDER_INSET);\n   styleAttrs.padding.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle1 = Style::create (&styleAttrs);\n\n   styleAttrs.backgroundColor = Color::create (layout, 0xffff80);\n   styleAttrs.margin.setVal (0);\n   styleAttrs.borderWidth.setVal (1);\n   styleAttrs.setBorderColor (Color::create (layout, 0x4040ff));\n   styleAttrs.setBorderStyle (BORDER_SOLID);\n   styleAttrs.padding.setVal (1);\n\n   Style *widgetStyle2 = Style::create (&styleAttrs);\n\n   Textblock *textblock1 = new Textblock (false);\n   textblock1->setStyle (widgetStyle1);\n   layout->setWidget (textblock1);\n\n   widgetStyle1->unref();\n\n   styleAttrs.borderWidth.setVal (0);\n   styleAttrs.padding.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.cursor = CURSOR_TEXT;\n\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   const char *words1[] = { \"Some\", \"random\", \"text.\", NULL };\n   const char *words2[] = { \"A\", \"nested\", \"paragraph.\", NULL };\n\n   for(int i = 0; words1[i]; i++) {\n      if(i != 0)\n         textblock1->addSpace (wordStyle);\n      textblock1->addText (words1[i], wordStyle);\n   }\n\n   for(int i = 0; i < 1; i++) {\n      textblock1->addParbreak(0, wordStyle);\n\n      Textblock *textblock2 = new Textblock (false);\n      textblock1->addWidget (textblock2, widgetStyle2);\n\n      for(int j = 0; words2[j]; j++) {\n         if(j != 0)\n            textblock2->addSpace (wordStyle);\n         textblock2->addText (words2[j], wordStyle);\n      }\n\n      textblock2->flush ();\n   }\n\n   textblock1->flush ();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   widgetStyle2->unref();\n   wordStyle->unref();\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_example.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl_Window.H>\n#include <FL/Fl.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n\n\nint main(int argc, char **argv)\n{\n   dw::fltk::FltkPlatform *platform = new dw::fltk::FltkPlatform ();\n   dw::core::Layout *layout = new dw::core::Layout (platform);\n\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Example\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   dw::fltk::FltkViewport *viewport =\n      new dw::fltk::FltkViewport (0, 0, 200, 300);\n   layout->attachView (viewport);\n\n   dw::core::style::StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   dw::core::style::FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = dw::core::style::FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = dw::core::style::FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color =\n      dw::core::style::Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor =\n      dw::core::style::Color::create (layout, 0xffffff);\n\n   dw::core::style::Style *widgetStyle =\n      dw::core::style::Style::create (&styleAttrs);\n\n   dw::Textblock *textblock = new dw::Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n\n   dw::core::style::Style *wordStyle =\n      dw::core::style::Style::create (&styleAttrs);\n\n   for(int i = 1; i <= 10; i++) {\n      char buf[4];\n      sprintf(buf, \"%d.\", i);\n\n      const char *words[] = { \"This\", \"is\", \"the\", buf, \"paragraph.\",\n                              \"Here\", \"comes\", \"some\", \"more\", \"text\",\n                              \"to\", \"demonstrate\", \"word\", \"wrapping.\",\n                              NULL };\n\n      for(int j = 0; words[j]; j++) {\n         textblock->addText(words[j], wordStyle);\n         textblock->addSpace(wordStyle);\n      }\n\n      textblock->addParbreak(10, wordStyle);\n   }\n\n   wordStyle->unref();\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_find_test.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n#include <FL/Fl_Box.H>\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n\nusing namespace lout::container::typed;\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nstatic FltkPlatform *platform;\nstatic Layout *layout;\nstatic Fl_Window *window;\nstatic FltkViewport *viewport;\nstatic Fl_Button *findButton, *resetButton;\nstatic Fl_Widget *resultLabel;\n\nstatic void findCallback (Fl_Widget *widget, void *data)\n{\n   //switch(layout->search (\"worm\", true)) {\n   switch(layout->search (\"WORM\", false, false)) {\n       case FindtextState::SUCCESS:\n          resultLabel->label(\"SUCCESS\");\n          break;\n\n       case FindtextState::RESTART:\n          resultLabel->label(\"RESTART\");\n          break;\n\n       case FindtextState::NOT_FOUND:\n          resultLabel->label(\"NOT_FOUND\");\n          break;\n   }\n\n   resultLabel->redraw ();\n}\n\nstatic void resetCallback (Fl_Widget *widget, void *data)\n{\n   layout->resetSearch ();\n   resultLabel->label(\"---\");\n   resultLabel->redraw ();\n}\n\nint main(int argc, char **argv)\n{\n   platform = new FltkPlatform ();\n   layout = new Layout (platform);\n\n   window = new Fl_Window(200, 300, \"Dw Find Test\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   viewport = new FltkViewport (0, 0, 200, 280);\n   viewport->end();\n   layout->attachView (viewport);\n\n   findButton = new Fl_Button(0, 280, 50, 20, \"Find\");\n   findButton->callback (findCallback, NULL);\n   findButton->when (FL_WHEN_RELEASE);\n   findButton->clear_visible_focus ();\n\n   resetButton = new Fl_Button(50, 280, 50, 20, \"Reset\");\n   resetButton->callback (resetCallback, NULL);\n   resetButton->when (FL_WHEN_RELEASE);\n   resetButton->clear_visible_focus ();\n\n   resultLabel = new Fl_Box(100, 280, 100, 20, \"---\");\n   resultLabel->box(FL_FLAT_BOX);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n   styleAttrs.margin.setVal (10);\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n   Style *topWidgetStyle = Style::create (&styleAttrs);\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.margin.left = 30;\n   styleAttrs.backgroundColor = NULL;\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   styleAttrs.margin.left = 0;\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (topWidgetStyle);\n   layout->setWidget (textblock);\n\n   Stack <Textblock> *stack = new Stack <Textblock> (false);\n   stack->push (textblock);\n\n   for(int i = 0; i < 10; i++)\n      for(int j = 0; j < 10; j++) {\n         Textblock *current;\n         if(j < 5) {\n            current = new Textblock (false);\n            stack->getTop()->addWidget (current, widgetStyle);\n            stack->push (current);\n         } else {\n            stack->getTop()->flush ();\n            stack->pop ();\n            current = stack->getTop ();\n         }\n\n         current->addText ((i == j ? \"worm\" : \"apple\"), wordStyle);\n         current->addLinebreak (wordStyle);\n      }\n\n   stack->getTop()->flush ();\n\n   topWidgetStyle->unref ();\n   widgetStyle->unref ();\n   wordStyle->unref ();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_float_test.cc",
    "content": "#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nstatic Textblock *firstFloat;\nstatic Style *wordStyle;\n\nstatic void addTextToFloatTimeout (void *data)\n{\n   printf(\"addTextToFloatTimeout\\n\");\n\n   const char *fWords[] = { \"This\", \"is\", \"a\", \"float,\", \"which\", \"is\",\n                            \"set\", \"aside\", \"from\", \"the\", \"main\",\n                            \"text.\", NULL };\n\n   for(int k = 0; fWords[k]; k++) {\n      firstFloat->addText(fWords[k], wordStyle);\n      firstFloat->addSpace(wordStyle);\n   }\n\n   firstFloat->flush();\n\n   Fl::repeat_timeout (2, addTextToFloatTimeout, NULL);\n}\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(400, 600, \"Dw Floats Example\");\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 400, 600);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   styleAttrs.font = core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   styleAttrs.borderWidth.setVal (1);\n   styleAttrs.setBorderColor (Color::create (layout, 0x808080));\n   styleAttrs.setBorderStyle (BORDER_DASHED);\n   styleAttrs.width = createAbsLength(100);\n   styleAttrs.vloat = FLOAT_LEFT;\n   Style *leftFloatStyle = Style::create (&styleAttrs);\n\n   styleAttrs.width = createAbsLength(80);\n   styleAttrs.vloat = FLOAT_RIGHT;\n   Style *rightFloatStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   widgetStyle->unref();\n\n   styleAttrs.borderWidth.setVal (0);\n   styleAttrs.width = LENGTH_AUTO;\n   styleAttrs.vloat = FLOAT_NONE;\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n\n   wordStyle = Style::create (&styleAttrs);\n\n   for(int i = 1; i <= 10; i++) {\n      char buf[16];\n      snprintf(buf, sizeof(buf), \"%d%s\",\n              i, (i == 1 ? \"st\" : (i == 2 ? \"nd\" : (i == 3 ? \"rd\" : \"th\"))));\n\n      const char *words[] = { \"This\", \"is\", \"the\", buf, \"paragraph.\",\n                              \"Here\", \"comes\", \"some\", \"more\", \"text\",\n                              \"to\", \"demonstrate\", \"word\", \"wrapping.\",\n                              NULL };\n\n      for(int j = 0; words[j]; j++) {\n         textblock->addText(words[j], wordStyle);\n         textblock->addSpace(wordStyle);\n\n         if ((i == 3 || i == 5) && j == 8) {\n            textblock->addText(\"[float]\", wordStyle);\n            textblock->addSpace(wordStyle);\n\n            Textblock *vloat = new Textblock (false);\n            textblock->addWidget(vloat, i == 3 ? leftFloatStyle : rightFloatStyle);\n\n            const char *fWords[] = { \"This\", \"is\", \"a\", \"float,\", \"which\", \"is\",\n                                     \"set\", \"aside\", \"from\", \"the\", \"main\",\n                                     \"text.\", NULL };\n\n            vloat->addText(i == 3 ? \"Left:\" : \"Right:\", wordStyle);\n            vloat->addSpace(wordStyle);\n\n            for(int k = 0; fWords[k]; k++) {\n               vloat->addText(fWords[k], wordStyle);\n               vloat->addSpace(wordStyle);\n            }\n\n            vloat->flush ();\n\n            if(i == 3)\n               firstFloat = vloat;\n         }\n      }\n\n      textblock->addParbreak(10, wordStyle);\n   }\n\n   leftFloatStyle->unref();\n   rightFloatStyle->unref();\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n   Fl::add_timeout (2, addTextToFloatTimeout, NULL);\n   int errorCode = Fl::run();\n\n   wordStyle->unref();\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_image_background.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2013 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <stdlib.h>\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n#include \"../dw/image.hh\"\n\nusing namespace lout::signal;\nusing namespace lout::misc;\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nclass ImageStyleDeletionReceiver: public ObservedObject::DeletionReceiver\n{\npublic:\n   void deleted (ObservedObject *object);\n};\n\nstatic StyleImage *image1 = NULL, *image2 = NULL;\nstatic Layout *layout;\nstatic int imgRow1 = 0, imgRow2 = 0;\nstatic ImageStyleDeletionReceiver isdr;\n\nvoid ImageStyleDeletionReceiver::deleted (ObservedObject *object)\n{\n   if ((StyleImage*)object == image1)\n      image1 = NULL;\n   else if ((StyleImage*)object == image2)\n      image2 = NULL;\n   else\n      assertNotReached ();\n}\n\nstatic void imageInitTimeout (void *data)\n{\n   if (image1) {\n      Imgbuf *imgbuf1 = layout->createImgbuf (Imgbuf::RGB, 400, 200, 1);\n      image1->getMainImgRenderer()->setBuffer (imgbuf1, false);\n   }\n\n   if (image2) {\n      Imgbuf *imgbuf2 = layout->createImgbuf (Imgbuf::RGB, 100, 100, 1);\n      image2->getMainImgRenderer()->setBuffer (imgbuf2, false);\n   }\n}\n\nstatic void imageDrawTimeout (void *data)\n{\n   Imgbuf *imgbuf1 = image1 ? image1->getImgbufSrc () : NULL;\n   Imgbuf *imgbuf2 = image2 ? image2->getImgbufSrc () : NULL;\n\n   if (imgbuf1 && imgRow1 < 200) {\n      byte buf[3 * 400];\n      for(int x = 0; x < 400; x++) {\n         buf[3 * x + 0] = 128 + x * 127 / 399;\n         buf[3 * x + 1] = 128 + (399 - x) * 127 / 399;\n         buf[3 * x + 2] = 128 + imgRow1 * 127 / 199;\n      }\n\n      imgbuf1->copyRow (imgRow1, buf);\n      image1->getMainImgRenderer()->drawRow (imgRow1);\n      imgRow1++;\n   }\n\n   if (imgbuf2 && imgRow2 < 100) {\n      byte buf[3 * 100];\n      for(int x = 0; x < 100; x++) {\n         int r = 128 + rand () % 127;\n         buf[3 * x + 0] = buf[3 * x + 1] = buf[3 * x + 2] = r;\n      }\n\n      imgbuf2->copyRow (imgRow2, buf);\n      image2->getMainImgRenderer()->drawRow (imgRow2);\n      imgRow2++;\n   }\n\n   if(imgRow1 < 200 || imgRow2 < 100)\n      Fl::repeat_timeout (0.5, imageDrawTimeout, NULL);\n}\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Example\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);\n   layout->attachView (viewport);\n\n   image1 = StyleImage::create ();\n   image1->connectDeletion (&isdr);\n   layout->setBgImage (image1, BACKGROUND_REPEAT_Y,\n                       BACKGROUND_ATTACHMENT_SCROLL, createPerLength (0.5),\n                       createAbsLength (30));\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n   styleAttrs.x_lang[0] = 'e';\n   styleAttrs.x_lang[1] = 'n';\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color =  Color::create (layout, 0x000000);\n   //styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.backgroundImage = NULL;\n\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   image2 = styleAttrs.backgroundImage = StyleImage::create ();\n   image2->connectDeletion (&isdr);\n   styleAttrs.backgroundRepeat = BACKGROUND_REPEAT;\n   styleAttrs.backgroundPositionX = createPerLength (0);\n   styleAttrs.backgroundPositionY = createPerLength (0);\n   Style *wordStyleBg = Style::create (&styleAttrs);\n\n   for(int i = 1; i <= 1; i++) {\n      char buf[4];\n      sprintf(buf, \"%d.\", i);\n\n      const char *words[] = { \"This\", \"is\", \"the\", buf, \"paragraph.\",\n                              \"Here\", \"comes\", \"some\", \"more\", \"text\",\n                              \"to\", \"demonstrate\", \"word\", \"wrapping.\",\n                              NULL };\n\n      for(int j = 0; words[j]; j++) {\n         textblock->addText(words[j], j == 11 ? wordStyleBg : wordStyle);\n         textblock->addSpace(wordStyle);\n      }\n\n      textblock->addParbreak(10, wordStyle);\n   }\n\n   wordStyle->unref();\n   wordStyleBg->unref();\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n\n   Fl::add_timeout (1.0, imageInitTimeout, NULL);\n   Fl::add_timeout (0.1, imageDrawTimeout, NULL);\n\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_images_scaled.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n#include \"../dw/image.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nstatic Layout *layout;\nstatic Image *image;\nstatic core::Imgbuf *imgbuf = NULL;\nstatic int imgRow = 0;\n\nstatic void imageInitTimeout (void *data)\n{\n   //imgbuf = layout->createImgbuf (Imgbuf::RGBA, 400, 200);\n   imgbuf = layout->createImgbuf (Imgbuf::RGB, 400, 200, 1);\n   image->setBuffer (imgbuf);\n}\n\n/*\nstatic void imageDrawTimeout (void *data)\n{\n   if (imgbuf) {\n      for (int i = 0; i < 1; i++) {\n         byte buf[4 * 400];\n         for(int x = 0; x < 400; x++) {\n            buf[4 * x + 0] = x * 255 / 399;\n            buf[4 * x + 1] = (399 - x) * 255 / 399;\n            buf[4 * x + 2] = imgRow * 255 / 199;\n            buf[4 * x + 3] = (199 - imgRow) * 255 / 199;\n         }\n\n         imgbuf->copyRow (imgRow, buf);\n         image->drawRow (imgRow);\n         imgRow++;\n      }\n   }\n\n   if(imgRow < 200)\n      ::fltk::repeat_timeout (0.5, imageDrawTimeout, NULL);\n}\n*/\n\nstatic void imageDrawTimeout (void *data)\n{\n   if (imgbuf) {\n      for (int i = 0; i < 1; i++) {\n         byte buf[3 * 400];\n         for(int x = 0; x < 400; x++) {\n            buf[3 * x + 0] = x * 255 / 399;\n            buf[3 * x + 1] = (399 - x) * 255 / 399;\n            buf[3 * x + 2] = imgRow * 255 / 199;\n         }\n\n         imgbuf->copyRow (imgRow, buf);\n         image->drawRow (imgRow);\n         imgRow++;\n      }\n   }\n\n   if(imgRow < 200)\n      Fl::repeat_timeout (0.5, imageDrawTimeout, NULL);\n}\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(410, 210, \"Dw Scaled Image\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 410, 210);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n   styleAttrs.width = createPerLength (1.0);\n   styleAttrs.height = createPerLength (1.0);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n\n   Style *imageStyle = Style::create (&styleAttrs);\n\n   image = new dw::Image (\"\");\n   textblock->addWidget (image, imageStyle);\n   textblock->addSpace (imageStyle);\n\n   imageStyle->unref();\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n\n   Fl::add_timeout (2.0, imageInitTimeout, NULL);\n   Fl::add_timeout (0.1, imageDrawTimeout, NULL);\n\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_images_scaled2.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n#include \"../dw/image.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nstatic Layout *layout;\nstatic Image *image1, *image2;\nstatic core::Imgbuf *imgbuf = NULL;\nstatic int imgRow = 0;\n\nstatic void imageInitTimeout (void *data)\n{\n   imgbuf = layout->createImgbuf (Imgbuf::RGB, 400, 200, 1);\n   image1->setBuffer (imgbuf);\n   image2->setBuffer (imgbuf);\n}\n\nstatic void imageDrawTimeout (void *data)\n{\n   if (imgbuf) {\n      for (int i = 0; i < 1; i++) {\n         byte buf[3 * 400];\n         for(int x = 0; x < 400; x++) {\n            buf[3 * x + 0] = x * 255 / 399;\n            buf[3 * x + 1] = (399 - x) * 255 / 399;\n            buf[3 * x + 2] = imgRow * 255 / 199;\n         }\n\n         imgbuf->copyRow (imgRow, buf);\n         image1->drawRow (imgRow);\n         image2->drawRow (imgRow);\n         imgRow++;\n      }\n   }\n\n   if(imgRow < 200)\n      Fl::repeat_timeout (0.5, imageDrawTimeout, NULL);\n}\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(410, 210, \"Dw Scaled Image 2\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 410, 210);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.borderWidth.setVal (0);\n   styleAttrs.padding.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   styleAttrs.borderWidth.setVal (1);\n   styleAttrs.setBorderColor (Color::create (layout, 0x000080));\n   styleAttrs.setBorderStyle (BORDER_SOLID);\n   styleAttrs.padding.setVal (1);\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.width = createPerLength (0.25);\n   styleAttrs.height = createPerLength (0.25);\n\n   Style *imageStyle1 = Style::create (&styleAttrs);\n   image1 = new dw::Image (\"A longer ALT Text to demonstrate clipping.\");\n   textblock->addWidget (image1, imageStyle1);\n   imageStyle1->unref();\n\n   textblock->addParbreak (10, wordStyle);\n\n   styleAttrs.width = LENGTH_AUTO;\n   styleAttrs.height = LENGTH_AUTO;\n\n   Style *imageStyle2 = Style::create (&styleAttrs);\n   image2 = new dw::Image (\"A longer ALT Text to demonstrate clipping.\");\n   textblock->addWidget (image2, imageStyle2);\n   imageStyle2->unref();\n\n   wordStyle->unref ();\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n\n   Fl::add_timeout (3.0, imageInitTimeout, NULL);\n   Fl::add_timeout (0.1, imageDrawTimeout, NULL);\n\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_images_simple.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n#include \"../dw/image.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nstatic Layout *layout;\nstatic Image *image;\nstatic core::Imgbuf *imgbuf = NULL;\nstatic int imgRow = 0;\n\nstatic void imageInitTimeout (void *data)\n{\n   const bool resize = true;\n   //imgbuf = layout->createImgbuf (Imgbuf::RGBA, 400, 200);\n   imgbuf = layout->createImgbuf (Imgbuf::RGB, 400, 200, 1);\n   image->setBuffer (imgbuf, resize);\n}\n\n/*\nstatic void imageDrawTimeout (void *data)\n{\n   if (imgbuf) {\n      for (int i = 0; i < 1; i++) {\n         byte buf[4 * 400];\n         for(int x = 0; x < 400; x++) {\n            buf[4 * x + 0] = x * 255 / 399;\n            buf[4 * x + 1] = (399 - x) * 255 / 399;\n            buf[4 * x + 2] = imgRow * 255 / 199;\n            buf[4 * x + 3] = (199 - imgRow) * 255 / 199;\n         }\n\n         imgbuf->copyRow (imgRow, buf);\n         image->drawRow (imgRow);\n         imgRow++;\n      }\n   }\n\n   if(imgRow < 200)\n      Fl::repeat_timeout (0.5, imageDrawTimeout, NULL);\n}\n*/\n\nstatic void imageDrawTimeout (void *data)\n{\n   if (imgbuf) {\n      for (int i = 0; i < 1; i++) {\n         byte buf[3 * 400];\n         for(int x = 0; x < 400; x++) {\n            buf[3 * x + 0] = x * 255 / 399;\n            buf[3 * x + 1] = (399 - x) * 255 / 399;\n            buf[3 * x + 2] = imgRow * 255 / 199;\n         }\n\n         imgbuf->copyRow (imgRow, buf);\n         image->drawRow (imgRow);\n         imgRow++;\n      }\n   }\n\n   if(imgRow < 200)\n      Fl::repeat_timeout (0.5, imageDrawTimeout, NULL);\n}\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(410, 210, \"Dw Simple Image\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 410, 210);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n\n   Style *imageStyle = Style::create (&styleAttrs);\n\n   image = new dw::Image (\"\");\n   textblock->addWidget (image, imageStyle);\n   textblock->addSpace (imageStyle);\n\n   imageStyle->unref();\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n\n   Fl::add_timeout (2.0, imageInitTimeout, NULL);\n   Fl::add_timeout (0.1, imageDrawTimeout, NULL);\n\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_imgbuf_mem_test.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n\nusing namespace lout::signal;\nusing namespace dw::core;\nusing namespace dw::fltk;\n\nvoid solution1 ()\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Imgbuf *rootbuf = layout->createImgbuf (Imgbuf::RGB, 100, 100, 1);\n   rootbuf->ref (); // Extra reference by the dicache.\n   printf (\"=== Can be deleted? %s.\\n\",\n           rootbuf->lastReference () ? \"Yes\" : \"No\");\n   Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50);\n   printf (\"=== Can be deleted? %s.\\n\",\n           rootbuf->lastReference () ? \"Yes\" : \"No\");\n   rootbuf->unref ();\n   printf (\"=== Can be deleted? %s.\\n\",\n           rootbuf->lastReference () ? \"Yes\" : \"No\");\n   scaledbuf->unref ();\n   printf (\"=== Can be deleted? %s.\\n\",\n           rootbuf->lastReference () ? \"Yes\" : \"No\");\n   rootbuf->unref (); // Extra reference by the dicache.\n\n   delete layout;\n}\n\nvoid solution2 ()\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Imgbuf *rootbuf = layout->createImgbuf (Imgbuf::RGB, 100, 100, 1);\n   rootbuf->setDeleteOnUnref (false);\n   printf (\"=== Can be deleted? %s.\\n\",\n           !rootbuf->isReferred () ? \"Yes\" : \"No\");\n   Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50);\n   printf (\"=== Can be deleted? %s.\\n\",\n           !rootbuf->isReferred () ? \"Yes\" : \"No\");\n   rootbuf->unref ();\n   printf (\"=== Can be deleted? %s.\\n\",\n           !rootbuf->isReferred () ? \"Yes\" : \"No\");\n   scaledbuf->unref ();\n   printf (\"=== Can be deleted? %s.\\n\",\n           !rootbuf->isReferred () ? \"Yes\" : \"No\");\n   delete rootbuf;\n\n   delete layout;\n}\n\nclass RootbufDeletionReceiver: public ObservedObject::DeletionReceiver\n{\n   void deleted (ObservedObject *object);\n};\n\nvoid RootbufDeletionReceiver::deleted (ObservedObject *object)\n{\n   printf (\"=== Is deleted now.\\n\");\n   delete this;\n}\n\nvoid solution3 ()\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Imgbuf *rootbuf = layout->createImgbuf (Imgbuf::RGB, 100, 100, 1);\n   rootbuf->connectDeletion (new RootbufDeletionReceiver ());\n   Imgbuf *scaledbuf = rootbuf->getScaledBuf (50, 50);\n   rootbuf->unref ();\n   scaledbuf->unref ();\n\n   delete layout;\n}\n\nint main (int argc, char **argv)\n{\n   printf (\"========== SOLUTION 1 ==========\\n\");\n   solution1 ();\n   printf (\"========== SOLUTION 2 ==========\\n\");\n   solution2 ();\n   printf (\"========== SOLUTION 3 ==========\\n\");\n   solution3 ();\n\n   return 0;\n}\n"
  },
  {
    "path": "test/dw_links.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nclass LinkTestReceiver: public Layout::LinkReceiver\n{\n   bool enter (Widget *widget, int link, int img, int x, int y);\n   bool press (Widget *widget, int link, int img, int x, int y,\n               EventButton *event);\n   bool release (Widget *widget, int link, int img, int x, int y,\n                 EventButton *event);\n   bool click (Widget *widget, int link, int img,\n               int x, int y, EventButton *event);\n};\n\nbool LinkTestReceiver::enter (Widget *widget, int link, int img, int x, int y)\n{\n   printf (\"enter: %d\\n\", link);\n   return true;\n}\n\nbool LinkTestReceiver::press (Widget *widget, int link, int img, int x, int y,\n                              EventButton *event)\n{\n   printf (\"press: %d\\n\", link);\n   return true;\n}\n\nbool LinkTestReceiver::release (Widget *widget, int link, int img, int x,int y,\n                                EventButton *event)\n{\n   printf (\"release: %d\\n\", link);\n   return true;\n}\n\nbool LinkTestReceiver::click (Widget *widget, int link, int img, int x, int y,\n                              EventButton *event)\n{\n   printf (\"click: %d\\n\", link);\n   return true;\n}\n\nint main(int argc, char **argv)\n{\n   LinkTestReceiver linkTestReceiver;\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Links\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   layout->connectLink (&linkTestReceiver);\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.cursor = CURSOR_TEXT;\n\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x0000ff);\n   styleAttrs.textDecoration = TEXT_DECORATION_UNDERLINE;\n   styleAttrs.cursor = CURSOR_POINTER;\n\n   for(int i = 1; i <= 10; i++) {\n      char buf[4];\n      sprintf(buf, \"%d.\", i);\n\n      const char *words1[] = {\n         \"This\", \"is\", \"the\", buf, \"paragraph.\",\n         \"Here\", \"comes\", \"some\", \"more\", \"text\",\n         \"to\", \"demonstrate\", \"word\", \"wrapping.\",\n         NULL };\n      const char *words2[] = {\n         \"Click\", \"here\", \"for\", \"more..\", NULL };\n\n      for(int j = 0; words1[j]; j++) {\n         textblock->addText(words1[j], wordStyle);\n         textblock->addSpace(wordStyle);\n      }\n\n      styleAttrs.x_link = i;\n      Style *linkStyle = Style::create (&styleAttrs);\n\n      for(int j = 0; words2[j]; j++) {\n         textblock->addText(words2[j], linkStyle);\n         textblock->addSpace(wordStyle);\n      }\n\n      linkStyle->unref ();\n\n      textblock->addParbreak(10, wordStyle);\n   }\n\n   wordStyle->unref();\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_links2.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n#include <FL/Fl_Box.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nclass LinkTestReceiver: public Layout::LinkReceiver\n{\n   bool enter (Widget *widget, int link, int img, int x, int y);\n   bool press (Widget *widget, int link, int img, int x, int y,\n               EventButton *event);\n   bool release (Widget *widget, int link, int img, int x, int y,\n                 EventButton *event);\n   bool click (Widget *widget, int link, int img, int x, int y,\n               EventButton *event);\n};\n\nbool LinkTestReceiver::enter (Widget *widget, int link, int img, int x, int y)\n{\n   printf (\"enter: %d\\n\", link);\n   return true;\n}\n\nbool LinkTestReceiver::press (Widget *widget, int link, int img, int x, int y,\n                              EventButton *event)\n{\n   printf (\"press: %d\\n\", link);\n   return true;\n}\n\nbool LinkTestReceiver::release (Widget *widget, int link, int img, int x,int y,\n                                EventButton *event)\n{\n   printf (\"release: %d\\n\", link);\n   return true;\n}\n\nbool LinkTestReceiver::click (Widget *widget, int link, int img, int x, int y,\n                              EventButton *event)\n{\n   printf (\"click: %d\\n\", link);\n   return true;\n}\n\nint main(int argc, char **argv)\n{\n   int MainIdx;\n   int ww = 200, wh = 300, lh = 24;\n\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Links2\");\n   window->box(FL_NO_BOX);\n   window->begin();\n    Fl_Widget *Panel = new Fl_Box(0, 0, ww, lh, \"CONTROL PANEL\");\n\n    Panel->color(FL_GRAY_RAMP + 3);\n    Panel->labelcolor(FL_WHITE);\n    Panel->box(FL_FLAT_BOX);\n    Fl_Widget *Main = new Fl_Box(0, lh, ww, wh - 2*lh, \"MAIN RENDERING AREA\");\n    Main->color(FL_GRAY_RAMP + 4);\n    Main->labelcolor(FL_WHITE);\n    MainIdx = window->find(Main);\n    /* status bar */\n    Fl_Widget *Bar = new Fl_Box(0, wh - lh, 200, lh, \"STATUS BAR...\");\n    Bar->color(FL_GRAY_RAMP + 3);\n    Bar->labelcolor(FL_WHITE);\n    Bar->box(FL_FLAT_BOX);\n\n    window->resizable(Main);\n   window->end();\n\n   //\n   // Create the main Dw and add some text there.\n   //\n   window->remove(MainIdx);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, lh, ww, wh - 2*lh);\n   layout->attachView (viewport);\n\n   window->end();\n\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   layout->connectLink (new LinkTestReceiver ());\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.cursor = CURSOR_TEXT;\n\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x0000ff);\n   styleAttrs.textDecoration = TEXT_DECORATION_UNDERLINE;\n   styleAttrs.cursor = CURSOR_POINTER;\n\n   for(int i = 1; i <= 30; i++) {\n      char buf[4];\n      sprintf(buf, \"%d.\", i);\n\n      const char *words1[] = {\n         \"This\", \"is\", \"the\", buf, \"paragraph.\",\n         \"Here\", \"comes\", \"some\", \"more\", \"text\",\n         \"to\", \"demonstrate\", \"word\", \"wrapping.\",\n         NULL };\n      const char *words2[] = {\n         \"Click\", \"here\", \"for\", \"more..\", NULL };\n\n      for(int j = 0; words1[j]; j++) {\n         textblock->addText (words1[j], wordStyle);\n         textblock->addSpace(wordStyle);\n      }\n\n      styleAttrs.x_link = i;\n      Style *linkStyle = Style::create (&styleAttrs);\n\n      for(int j = 0; words2[j]; j++) {\n         textblock->addText (words2[j], linkStyle);\n         textblock->addSpace(wordStyle);\n      }\n\n      linkStyle->unref ();\n\n      textblock->addParbreak(10, wordStyle);\n   }\n\n   wordStyle->unref();\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_lists.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n#include \"../dw/listitem.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Lists\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.cursor = CURSOR_TEXT;\n\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   styleAttrs.margin.setVal (5);\n   styleAttrs.padding.setVal (5);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffff40);\n   styleAttrs.setBorderColor (Color::create (layout, 0x000000));\n   styleAttrs.setBorderStyle (BORDER_SOLID);\n   styleAttrs.borderWidth.setVal (1);\n\n   Style *itemStyle = Style::create (&styleAttrs);\n\n   const char *wordsPar[] = {\n      \"This\", \"is\", \"a\", \"normal\", \"paragraph.\", \"And\",\n      \"some\", \"list\", \"items\", \"follow:\", NULL };\n   const char *wordsItem[] = {\n      \"This\", \"is\", \"a\", \"list\", \"item.\", \"Here\",\n      \"comes\", \"some\", \"more\", \"text\", \"to\",\n      \"demonstrate\", \"word\", \"wrapping.\", NULL };\n\n\n   for(int i = 0; wordsPar[i]; i++) {\n      if(i != 0)\n         textblock->addSpace (wordStyle);\n      textblock->addText (wordsPar[i], wordStyle);\n   }\n   textblock->addParbreak (5, wordStyle);\n\n   ListItem *refItem = NULL;\n\n   for(int i = 1; i <= 100; i++) {\n      ListItem *listItem = new ListItem (refItem, false);\n      refItem = listItem;\n\n      textblock->addWidget (listItem, itemStyle);\n      textblock->addParbreak (2, wordStyle);\n\n      char buf[16];\n      sprintf (buf, \"%d.\", i);\n      listItem->initWithText (buf, wordStyle);\n\n      for(int j = 0; wordsItem[j]; j++) {\n         if(j != 0)\n            listItem->addSpace (wordStyle);\n         listItem->addText (wordsItem[j], wordStyle);\n      }\n\n      listItem->flush ();\n   }\n\n   wordStyle->unref();\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_resource_test.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/textblock.hh\"\n#include \"../dw/ui.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::core::ui;\nusing namespace dw::fltk;\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(410, 210, \"Dw Resource test\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 410, 210);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *widgetStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock = new Textblock (false);\n   textblock->setStyle (widgetStyle);\n   layout->setWidget (textblock);\n\n   widgetStyle->unref();\n\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n\n   widgetStyle = Style::create (&styleAttrs);\n\n   SelectionResource *res = layout->getResourceFactory()->createListResource\n      (ListResource::SELECTION_AT_MOST_ONE, 4);\n   //SelectionResource *res =\n   //   layout->getResourceFactory()->createOptionMenuResource ();\n\n   Embed *embed = new Embed (res);\n   textblock->addWidget (embed, widgetStyle);\n   textblock->addSpace (widgetStyle);\n\n   widgetStyle->unref();\n\n   for(int i = 0; i < 50; i++)\n      res->addItem (\"Hello, world!\", true, i == 0 ? true : false);\n\n   textblock->flush ();\n\n   window->resizable(viewport);\n   window->show();\n\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_simple_container.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n#include <math.h>\n\n#include \"dw_simple_container.hh\"\n\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace lout::misc;\n\nnamespace dw {\n\nint SimpleContainer::CLASS_ID = -1;\n\n// ----------------------------------------------------------------------\n\nSimpleContainer::SimpleContainerIterator::SimpleContainerIterator\n(SimpleContainer *simpleContainer, Content::Type mask, bool atEnd) :\n   Iterator (simpleContainer, mask, atEnd)\n{\n   content.type = atEnd ? Content::END : Content::START;\n}\n\nlout::object::Object *SimpleContainer::SimpleContainerIterator::clone ()\n{\n   SimpleContainerIterator *sci =\n      new SimpleContainerIterator ((SimpleContainer*)getWidget(),\n                                   getMask(), false);\n   sci->content = content;\n   return sci;\n}\n\nint SimpleContainer::SimpleContainerIterator::index ()\n{\n   switch (content.type) {\n   case Content::START:\n      return 0;\n   case Content::WIDGET_IN_FLOW:\n      return 1;\n   case Content::END:\n      return 2;\n   default:\n      assertNotReached ();\n      return 0;\n   }\n}\n\nint SimpleContainer::SimpleContainerIterator::compareTo\n(lout::object::Comparable *other)\n{\n   return index () - ((SimpleContainerIterator*)other)->index ();\n}\n\nbool SimpleContainer::SimpleContainerIterator::next ()\n{\n   SimpleContainer *simpleContainer = (SimpleContainer*)getWidget();\n\n   if (content.type == Content::END)\n      return false;\n\n   // simple containers only contain widgets:\n   if ((getMask() & Content::WIDGET_IN_FLOW) == 0) {\n      content.type = Content::END;\n      return false;\n   }\n\n   if (content.type == Content::START) {\n      if (simpleContainer->child != NULL) {\n         content.type = Content::WIDGET_IN_FLOW;\n         content.widget = simpleContainer->child;\n         return true;\n      } else {\n         content.type = Content::END;\n         return false;\n      }\n   } else /* if (content.type == Content::WIDGET) */ {\n      content.type = Content::END;\n      return false;\n   }\n}\n\nbool SimpleContainer::SimpleContainerIterator::prev ()\n{\n   SimpleContainer *simpleContainer = (SimpleContainer*)getWidget();\n\n   if (content.type == Content::START)\n      return false;\n\n   // simple containers only contain widgets:\n   if ((getMask() & Content::WIDGET_IN_FLOW) == 0) {\n      content.type = Content::START;\n      return false;\n   }\n\n   if (content.type == Content::END) {\n      if (simpleContainer->child != NULL) {\n         content.type = Content::WIDGET_IN_FLOW;\n         content.widget = simpleContainer->child;\n         return true;\n      } else {\n         content.type = Content::START;\n         return false;\n      }\n   } else /* if (content.type == Content::WIDGET) */ {\n      content.type = Content::START;\n      return false;\n   }\n}\n\nvoid SimpleContainer::SimpleContainerIterator::highlight (int start,\n                                                          int end,\n                                                          HighlightLayer layer)\n{\n   /** todo Needs this an implementation? */\n}\n\nvoid SimpleContainer::SimpleContainerIterator::unhighlight (int direction,\n                                                            HighlightLayer\n                                                            layer)\n{\n   /** todo Needs this an implementation? */\n}\n\nvoid SimpleContainer::SimpleContainerIterator::getAllocation (int start,\n                                                              int end,\n                                                              Allocation\n                                                              *allocation)\n{\n   /** \\bug Not implemented. */\n}\n\n// ----------------------------------------------------------------------\n\nSimpleContainer::SimpleContainer ()\n{\n   registerName (\"dw::SimpleContainer\", &CLASS_ID);\n   child = NULL;\n}\n\nSimpleContainer::~SimpleContainer ()\n{\n   if (child)\n      delete child;\n}\n\nvoid SimpleContainer::sizeRequestSimpl (Requisition *requisition)\n{\n   Requisition childReq;\n   if (child)\n      child->sizeRequest (&childReq);\n   else\n      childReq.width = childReq.ascent = childReq.descent = 0;\n\n   requisition->width = childReq.width + boxDiffWidth ();\n   requisition->ascent = childReq.ascent + boxOffsetY ();\n   requisition->descent = childReq.descent + boxRestHeight ();\n\n   correctRequisition (requisition, splitHeightPreserveAscent, true, true);\n}\n\n\nvoid SimpleContainer::getExtremesSimpl (Extremes *extremes)\n{\n   Extremes childExtr;\n   if (child)\n      child->getExtremes (&childExtr);\n   else\n      childExtr.minWidth = childExtr.minWidthIntrinsic = childExtr.maxWidth =\n         childExtr.maxWidthIntrinsic = extremes->adjustmentWidth = 0;\n   \n   extremes->minWidth = childExtr.minWidth + boxDiffWidth ();\n   extremes->minWidthIntrinsic = childExtr.minWidthIntrinsic + boxDiffWidth ();\n   extremes->maxWidth = childExtr.maxWidth + boxDiffWidth ();\n   extremes->maxWidthIntrinsic = childExtr.maxWidthIntrinsic + boxDiffWidth ();\n   extremes->adjustmentWidth = childExtr.adjustmentWidth + boxDiffWidth ();\n\n   correctExtremes (extremes, true);\n}\n\nvoid SimpleContainer::sizeAllocateImpl (Allocation *allocation)\n{\n   Allocation childAlloc;\n\n   if (child) {\n      childAlloc.x = allocation->x + boxOffsetX ();\n      childAlloc.y = allocation->y + boxOffsetY ();\n      childAlloc.width = allocation->width - boxDiffWidth ();\n      childAlloc.ascent = allocation->ascent - boxOffsetY ();\n      childAlloc.descent = allocation->descent - boxRestHeight ();\n      child->sizeAllocate (&childAlloc);\n   }\n}\n\nvoid SimpleContainer::draw (View *view, Rectangle *area,\n                            DrawingContext *context)\n{\n   drawWidgetBox (view, area, false);\n   Rectangle childArea;\n   if (child && child->intersects (this, area, &childArea))\n      child->draw (view, &childArea, context);\n}\n\nIterator *SimpleContainer::iterator (Content::Type mask, bool atEnd)\n{\n   return new SimpleContainerIterator (this, mask, atEnd);\n}\n\nvoid SimpleContainer::removeChild (Widget *child)\n{\n   assert (child == this->child);\n   this->child = NULL;\n\n   queueResize (0, true);\n}\n\nvoid SimpleContainer::setChild (Widget *child)\n{\n   if (this->child)\n      delete this->child;\n\n   this->child = child;\n   if (this->child)\n      this->child->setParent (this);\n\n   queueResize (0, true);\n}\n\n} // namespace dw\n"
  },
  {
    "path": "test/dw_simple_container.hh",
    "content": "#ifndef __DW_SIMPLE_CONTAINER_HH__\n#define __DW_SIMPLE_CONTAINER_HH__\n\n#include \"dw/core.hh\"\n\nnamespace dw {\n\n/**\n * Simple widget used for testing concepts.\n */\nclass SimpleContainer: public core::Widget\n{\nprivate:\n   class SimpleContainerIterator: public core::Iterator\n   {\n   private:\n      int index ();\n\n   public:\n      SimpleContainerIterator (SimpleContainer *simpleContainer,\n                               core::Content::Type mask,\n                      bool atEnd);\n\n      lout::object::Object *clone ();\n      int compareTo (lout::object::Comparable *other);\n\n      bool next ();\n      bool prev ();\n      void highlight (int start, int end, core::HighlightLayer layer);\n      void unhighlight (int direction, core::HighlightLayer layer);\n      void getAllocation (int start, int end, core::Allocation *allocation);\n   };\n\n   Widget *child;\n\nprotected:\n   void sizeRequestSimpl (core::Requisition *requisition);\n   void getExtremesSimpl (core::Extremes *extremes);\n   void sizeAllocateImpl (core::Allocation *allocation);\n\npublic:\n   static int CLASS_ID;\n\n   SimpleContainer ();\n   ~SimpleContainer ();\n\n   void draw (core::View *view, core::Rectangle *area,\n              core::DrawingContext *context);\n   core::Iterator *iterator (core::Content::Type mask, bool atEnd);\n   void removeChild (Widget *child);\n\n   void setChild (core::Widget *child);\n};\n\n} // namespace dw\n\n#endif // __DW_SIMPLE_CONTAINER_HH__\n"
  },
  {
    "path": "test/dw_simple_container_test.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2014 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl_Window.H>\n#include <FL/Fl.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"dw_simple_container.hh\"\n#include \"../dw/textblock.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Example\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   Style *textblockStyle1 = Style::create (&styleAttrs);\n\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.margin.setVal (0);\n\n   Style *textblockStyle2 = Style::create (&styleAttrs);\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   styleAttrs.borderWidth.setVal (5);\n   styleAttrs.setBorderColor (Color::create (layout, 0x800080));\n   styleAttrs.setBorderStyle (BORDER_DASHED);\n   styleAttrs.padding.setVal (5);\n\n   Style *containerStyle = Style::create (&styleAttrs);\n\n   Textblock *textblock1 = new Textblock (false);\n   textblock1->setStyle (textblockStyle1);\n   layout->setWidget (textblock1);\n\n   SimpleContainer *simpleContainer = new SimpleContainer ();\n   simpleContainer->setStyle (containerStyle);\n   textblock1->addWidget (simpleContainer, containerStyle);\n\n   Textblock *textblock2 = new Textblock (false);\n   textblock2->setStyle (textblockStyle2);\n   simpleContainer->setChild (textblock2);\n\n   const char *words[] = { \"This\", \"is\", \"only\", \"a\", \"short\", \"paragraph.\",\n                           NULL };\n\n   for(int j = 0; words[j]; j++) {\n      textblock2->addText(words[j], wordStyle);\n      textblock2->addSpace(wordStyle);\n   }\n\n   textblockStyle1->unref();\n   textblockStyle2->unref();\n   containerStyle->unref();\n   wordStyle->unref();\n\n   textblock1->flush ();\n   textblock2->flush ();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_table.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/table.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(300, 300, \"Dw Table\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 300, 300);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n   styleAttrs.padding.setVal (0);\n   styleAttrs.borderWidth.setVal (1);\n   styleAttrs.setBorderStyle (BORDER_OUTSET);\n   styleAttrs.setBorderColor (Color::create (layout, 0xffffff));\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n   styleAttrs.hBorderSpacing = 5;\n   styleAttrs.vBorderSpacing = 5;\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   Style *tableStyle = Style::create (&styleAttrs);\n\n   Table *table = new Table (false);\n   table->setStyle (tableStyle);\n   layout->setWidget (table);\n\n   tableStyle->unref();\n\n   styleAttrs.setBorderStyle (BORDER_INSET);\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.margin.setVal (0);\n   styleAttrs.padding.setVal (5);\n\n   Style *cellStyle = Style::create (&styleAttrs);\n\n   styleAttrs.borderWidth.setVal (0);\n   styleAttrs.margin.setVal (0);\n   styleAttrs.cursor = CURSOR_TEXT;\n   styleAttrs.textAlignChar = '.';\n\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   for (int i = 0; i < 4; i++) {\n      table->addRow (wordStyle);\n\n      for (int j = 0; j < 4; j++) {\n         Textblock *cell = new Textblock (false);\n         cell->setStyle (cellStyle);\n         table->addCell (cell, 1, 1);\n\n         char buf[10];\n         sprintf (buf, \"cell %c\", 'A' + 4 * i + j);\n\n         cell->addText (buf, wordStyle);\n         cell->flush ();\n      }\n   }\n\n   wordStyle->unref();\n   cellStyle->unref();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_table_aligned.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/table.hh\"\n#include \"../dw/alignedtablecell.hh\"\n\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::fltk;\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(200, 300, \"Dw Table Aligned\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 200, 300);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n   styleAttrs.borderWidth.setVal (1);\n   styleAttrs.setBorderStyle (BORDER_OUTSET);\n   styleAttrs.setBorderColor (Color::create (layout, 0x808080));\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Bitstream Charter\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xa0a0a0);\n   styleAttrs.hBorderSpacing = 5;\n   styleAttrs.vBorderSpacing = 5;\n\n   Style *tableStyle = Style::create (&styleAttrs);\n\n   Table *table = new Table (false);\n   table->setStyle (tableStyle);\n   layout->setWidget (table);\n\n   tableStyle->unref();\n\n   styleAttrs.borderWidth.setVal (1);\n   styleAttrs.setBorderStyle (BORDER_INSET);\n\n   Style *cellStyle = Style::create (&styleAttrs);\n\n   styleAttrs.borderWidth.setVal (0);\n   styleAttrs.margin.setVal (0);\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.cursor = CURSOR_TEXT;\n   styleAttrs.textAlignChar = '.';\n\n   Style *wordStyle = Style::create (&styleAttrs);\n\n   AlignedTableCell *ref = NULL;\n   for(int i = 0; i < 10; i++) {\n      //for(int i = 0; i < 1; i++) {\n      AlignedTableCell *cell = new AlignedTableCell (ref, false);\n      cell->setStyle (cellStyle);\n      ref = cell;\n      table->addRow (wordStyle);\n      table->addCell (cell, 1, 1);\n\n      char buf[16];\n      for(int j = 0; j < i; j++)\n         buf[j] = '0' + j;\n      buf[i] = '.';\n      for(int j = i + 1; j < 11; j++)\n         buf[j] = '0' + (j - 1);\n      buf[11] = 0;\n\n      cell->addText (buf, wordStyle);\n      cell->flush ();\n   }\n\n   wordStyle->unref();\n   cellStyle->unref();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/dw_ui_test.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include <FL/Fl.H>\n#include <FL/Fl_Window.H>\n\n#include \"../dw/core.hh\"\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/fltkviewport.hh\"\n#include \"../dw/table.hh\"\n#include \"../dw/textblock.hh\"\n#include \"../dw/ui.hh\"\n#include \"form.hh\"\n\nusing namespace lout::object;\nusing namespace lout::container::typed;\nusing namespace dw;\nusing namespace dw::core;\nusing namespace dw::core::style;\nusing namespace dw::core::ui;\nusing namespace dw::fltk;\n\nint main(int argc, char **argv)\n{\n   FltkPlatform *platform = new FltkPlatform ();\n   Layout *layout = new Layout (platform);\n\n   Fl_Window *window = new Fl_Window(400, 400, \"Dw UI Test\");\n   window->box(FL_NO_BOX);\n   window->begin();\n\n   FltkViewport *viewport = new FltkViewport (0, 0, 400, 400);\n   layout->attachView (viewport);\n\n   StyleAttrs styleAttrs;\n   styleAttrs.initValues ();\n   styleAttrs.margin.setVal (5);\n   styleAttrs.color = Color::create (layout, 0x000000);\n   styleAttrs.backgroundColor = Color::create (layout, 0xffffff);\n\n   FontAttrs fontAttrs;\n   fontAttrs.name = \"Helvetica\";\n   fontAttrs.size = 14;\n   fontAttrs.weight = 400;\n   fontAttrs.style = FONT_STYLE_NORMAL;\n   fontAttrs.letterSpacing = 0;\n   fontAttrs.fontVariant = FONT_VARIANT_NORMAL;\n   styleAttrs.font = dw::core::style::Font::create (layout, &fontAttrs);\n\n   Style *tableStyle = Style::create (&styleAttrs);\n\n   Table *table = new Table (false);\n   table->setStyle (tableStyle);\n   layout->setWidget (table);\n\n   tableStyle->unref();\n\n   styleAttrs.backgroundColor = NULL;\n   styleAttrs.margin.setVal (0);\n\n   Style *cellStyle = Style::create (&styleAttrs);\n\n   // First of all, the resources. Later, they are embedded into the\n   // widget tree.\n   EntryResource *entryres1 =\n      layout->getResourceFactory()->createEntryResource (10, false, NULL,NULL);\n   entryres1->setText (\"Hi!\");\n   EntryResource *entryres2 =\n      layout->getResourceFactory()->createEntryResource (10, true, NULL,\n                                                         \"password field!\");\n   MultiLineTextResource *textres =\n      layout->getResourceFactory()->createMultiLineTextResource (15,3,\n                                                      \"textarea placeholder!\");\n   RadioButtonResource *radiores1 =\n      layout->getResourceFactory()->createRadioButtonResource (NULL, false);\n   RadioButtonResource *radiores2 =\n      layout->getResourceFactory()->createRadioButtonResource (radiores1,\n                                                               false);\n   CheckButtonResource *checkres =\n      layout->getResourceFactory()->createCheckButtonResource (true);\n   SelectionResource *selres[2];\n   selres[0] = layout->getResourceFactory()->createOptionMenuResource ();\n   selres[1] = layout->getResourceFactory()->createListResource\n      (ListResource::SELECTION_AT_MOST_ONE, 4);\n   LabelButtonResource *buttonres =\n      layout->getResourceFactory()->createLabelButtonResource (\"Run!\");\n\n   // Note on complex buttons: before any operations on the widget, which\n   // need a layout, the complex button resource should be created, since\n   // then, a layout and a platform are instantiated.\n   Textblock *cbuttontext = new Textblock(false);\n   ComplexButtonResource *cbuttonres =\n      layout->getResourceFactory()->createComplexButtonResource (cbuttontext,\n                                                                 true);\n   cbuttontext->setStyle (cellStyle);\n   cbuttontext->addText (\"Run (complex)!\", cellStyle);\n   cbuttontext->flush ();\n\n   // The entry resources are put into a special handler, which is\n   // also a receiver for the button resources.\n   form::Form *form = new form::Form();\n   form->addTextResource (\"val1\", entryres1);\n   form->addTextResource (\"val2\", entryres2);\n   form->addTextResource (\"text\", textres);\n   const char *radiovalues[] = { \"radio1\", \"radio2\", NULL };\n   form->addRadioButtonResource (\"val3\", radiores1, radiovalues);\n   form->addCheckButtonResource (\"check\", checkres);\n   const char *selvalues[] = { \"i1\", \"g1\", \"i11\", \"i12\", \"i13\", \"(pop)\", \"i2\",\n                               \"g2\", \"i21\", \"i22\", \"i23\", \"(pop)\", \"i3\", NULL};\n   form->addSelectionResource (\"val4\", selres[0], selvalues);\n   form->addSelectionResource (\"val5\", selres[1], selvalues);\n   form->addButtonResource (\"button\", buttonres, \"Run!\");\n   form->addButtonResource (\"cbutton\", cbuttonres, \"cbuttonval\");\n\n   // Create the widgets.\n   table->addRow (cellStyle);\n\n   Textblock *label1 = new Textblock(false);\n   label1->setStyle (cellStyle);\n   table->addCell (label1, 1, 1);\n   label1->addText (\"val1 = \", cellStyle);\n   label1->flush ();\n\n   Embed *input1 = new Embed (entryres1);\n   input1->setStyle (cellStyle);\n   table->addCell (input1, 1, 1);\n\n   table->addRow (cellStyle);\n\n   Textblock *label2 = new Textblock(false);\n   label2->setStyle (cellStyle);\n   table->addCell (label2, 1, 1);\n   label2->addText (\"val2 = \", cellStyle);\n   label2->flush ();\n\n   Embed *input2 = new Embed (entryres2);\n   input2->setStyle (cellStyle);\n   table->addCell (input2, 1, 1);\n\n   table->addRow (cellStyle);\n\n   Textblock *label = new Textblock(false);\n   label->setStyle (cellStyle);\n   table->addCell (label, 1, 1);\n   label->addText (\"text = \", cellStyle);\n   label->flush ();\n\n   Embed *text = new Embed (textres);\n   textres->setText(\"Hi textarea\");\n   text->setStyle (cellStyle);\n   table->addCell (text, 1, 1);\n\n   table->addRow (cellStyle);\n\n   Textblock *radiolabel1 = new Textblock(false);\n   radiolabel1->setStyle (cellStyle);\n   table->addCell (radiolabel1, 2, 1);\n   Embed *radio1 = new Embed (radiores1);\n   radiolabel1->addWidget (radio1, cellStyle);\n   radiolabel1->addText (\" radio1\", cellStyle);\n   radiolabel1->flush ();\n\n   table->addRow (cellStyle);\n   Textblock *radiolabel2 = new Textblock(false);\n   radiolabel2->setStyle (cellStyle);\n   table->addCell (radiolabel2, 2, 1);\n   Embed *radio2 = new Embed (radiores2);\n   radiolabel2->addWidget (radio2, cellStyle);\n   radiolabel2->addText (\" radio2\", cellStyle);\n   radiolabel2->flush ();\n\n   table->addRow (cellStyle);\n   Textblock *checklabel = new Textblock(false);\n   checklabel->setStyle (cellStyle);\n   table->addCell (checklabel, 2, 1);\n   Embed *check = new Embed (checkres);\n   checklabel->addWidget (check, cellStyle);\n   checklabel->addText (\" check\", cellStyle);\n   checklabel->flush ();\n\n   for(int i = 0; i < 2; i++) {\n      table->addRow (cellStyle);\n\n      Embed *sel = new Embed (selres[i]);\n      sel->setStyle (cellStyle);\n      table->addCell (sel, 2, 1);\n\n      selres[i]->addItem(\"item 1\", true, false);\n\n      selres[i]->pushGroup(\"group 1\", true);\n      selres[i]->addItem(\"item 1/1\", true, false);\n      selres[i]->addItem(\"item 1/2\", true, true);\n      selres[i]->addItem(\"item 1/3\", false, false);\n      selres[i]->popGroup();\n\n      selres[i]->addItem(\"item 2\", false, i == 1);\n\n      selres[i]->pushGroup(\"group 2\", true);\n      selres[i]->addItem(\"item 2/1\", true, false);\n      selres[i]->addItem(\"item 2/2\", true, false);\n      selres[i]->addItem(\"item 2/3\", false, false);\n      selres[i]->popGroup();\n\n      selres[i]->addItem(\"item 3\", false, false);\n   }\n\n   table->addRow (cellStyle);\n   Embed *button = new Embed (buttonres);\n   button->setStyle (cellStyle);\n   table->addCell (button, 2, 1);\n\n   table->addRow (cellStyle);\n   Embed *cbutton = new Embed (cbuttonres);\n   cbutton->setStyle (cellStyle);\n   table->addCell (cbutton, 2, 1);\n\n   cellStyle->unref();\n\n   window->resizable(viewport);\n   window->show();\n   int errorCode = Fl::run();\n\n   delete form;\n   delete layout;\n\n   return errorCode;\n}\n"
  },
  {
    "path": "test/floats-and-absolute.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html>\n  <head>\n    <title>Floats And Absolute Positions</title>\n    <style type=\"text/css\">\n      div.main {\n        margin: 0 0 0 100px;\n        top: 3cm;\n        position: absolute;\n      }\n\n      div.margin {\n        position: absolute;\n        top: 3cm;\n        width: 120px;\n      }\n    </style>\n  </head>\n  <body>\n    <h1>Floats And Absolute Positions</h1>\n    <div class=\"main\">\n    <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\" />\n      <p>Sed ut perspiciatis, unde omnis iste natus error sit\n        voluptatem accusantium doloremque laudantium, totam rem\n        aperiam eaque ipsa, quae ab illo inventore veritatis et quasi\n        architecto beatae vitae dicta sunt, explicabo. nemo enim ipsam\n        voluptatem, quia voluptas sit, aspernatur aut odit aut fugit,\n        sed quia consequuntur magni dolores eos, qui ratione\n        voluptatem sequi nesciunt, neque porro quisquam est, qui\n        dolorem ipsum, quia dolor sit, amet, consectetur, adipisci\n        velit, sed quia non numquam eius modi tempora incidunt, ut\n        labore et dolore magnam aliquam quaerat voluptatem. ut enim ad\n        minima veniam, quis nostrum exercitationem ullam corporis\n        suscipit laboriosam, nisi ut aliquid ex ea commodi\n        consequatur? quis autem vel eum iure reprehenderit, qui in ea\n        voluptate velit esse, quam nihil molestiae consequatur, vel\n        illum, qui dolorem eum fugiat, quo voluptas nulla\n        pariatur?</p>\n      <p>Ἐν ἀρχῇ ἦν ὁ Λόγος, καὶ ὁ Λόγος ἦν πρὸς τὸν Θεόν, καὶ Θεὸς ἦν\n        ὁ Λόγος. Οὗτος ἦν ἐν ἀρχῇ πρὸς τὸν Θεόν. πάντα δι' αὐτοῦ\n        ἐγένετο, καὶ χωρὶς αὐτοῦ ἐγένετο οὐδὲ ἕν ὃ γέγονεν. ἐν αὐτῷ\n        ζωὴ ἦν, καὶ ἡ ζωὴ ἦν τὸ φῶς τῶν ἀνθρώπων. καὶ τὸ φῶς ἐν τῇ\n        σκοτίᾳ φαίνει, καὶ ἡ σκοτία αὐτὸ οὐ κατέλαβεν.</p>\n    </div>\n    <div class=\"margin\">Margin, actually on the left side.</div>\n  <body>\n</html>\n\n\n\n"
  },
  {
    "path": "test/floats-and-margins.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html>\n  <head>\n    <title>Floats And Margins</title>\n    <style type=\"text/css\">\n    </style>\n  </head>\n  <body>\n    <p>\n      <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\" />\n      Ἐν ἀρχῇ ἦν ὁ Λόγος, καὶ ὁ Λόγος ἦν πρὸς τὸν Θεόν, καὶ Θεὸς ἦν ὁ\n      Λόγος. Οὗτος ἦν ἐν ἀρχῇ πρὸς τὸν Θεόν. πάντα δι' αὐτοῦ ἐγένετο,\n      καὶ χωρὶς αὐτοῦ ἐγένετο οὐδὲ ἕν ὃ γέγονεν. ἐν αὐτῷ ζωὴ ἦν, καὶ ἡ\n      ζωὴ ἦν τὸ φῶς τῶν ἀνθρώπων. καὶ τὸ φῶς ἐν τῇ σκοτίᾳ φαίνει, καὶ\n      ἡ σκοτία αὐτὸ οὐ κατέλαβεν.\n    </p>\n    <p style=\"margin: 0 3cm\">\n      <img src=\"http://upload.wikimedia.org/wikipedia/commons/thumb/2/26/GilgameshTablet.jpg/200px-GilgameshTablet.jpg\" style=\"float: right; margin: 1cm 0 1cm 1cm\"/>\n      Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\n      accusantium doloremque laudantium, totam rem aperiam eaque ipsa,\n      quae ab illo inventore veritatis et quasi architecto beatae\n      vitae dicta sunt, explicabo. nemo enim ipsam voluptatem, quia\n      voluptas sit, aspernatur aut odit aut fugit, sed quia\n      consequuntur magni dolores eos, qui ratione voluptatem sequi\n      nesciunt, neque porro quisquam est, qui dolorem ipsum, quia\n      dolor sit, amet, consectetur, adipisci velit, sed quia non\n      numquam eius modi tempora incidunt, ut labore et dolore magnam\n      aliquam quaerat voluptatem. ut enim ad minima veniam, quis\n      nostrum exercitationem ullam corporis suscipit laboriosam, nisi\n      ut aliquid ex ea commodi consequatur? quis autem vel eum iure\n      reprehenderit, qui in ea voluptate velit esse, quam nihil\n      molestiae consequatur, vel illum, qui dolorem eum fugiat, quo\n      voluptas nulla pariatur?\n    </p>\n  <body>\n</html>\n\n\n\n"
  },
  {
    "path": "test/floats-table.html",
    "content": "<p>Demonstrating how to include floats into witdth extremes.\n\n<table>\n  <tr>\n    <td style=\"border: 1px dashed black\">\n      <div style=\"float:left; border: 1px dashed black\">Somelongwordwhichmustnotbebrokensotakingmuchspaceinatablecolumn</div>\n      Some short text.\n    <td style=\"border: 1px dashed black\">\n      Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\n      accusantium doloremque laudantium, totam rem aperiam eaque ipsa,\n      quae ab illo inventore veritatis et quasi architecto beatae\n      vitae dicta sunt, explicabo. nemo enim ipsam voluptatem, quia\n      voluptas sit, aspernatur aut odit aut fugit, sed quia\n      consequuntur magni dolores eos, qui ratione voluptatem sequi\n      nesciunt, neque porro quisquam est, qui dolorem ipsum, quia\n      dolor sit, amet, consectetur, adipisci velit, sed quia non\n      numquam eius modi tempora incidunt, ut labore et dolore magnam\n      aliquam quaerat voluptatem. ut enim ad minima veniam, quis\n      nostrum exercitationem ullam corporis suscipit laboriosam, nisi\n      ut aliquid ex ea commodi consequatur? quis autem vel eum iure\n      reprehenderit, qui in ea voluptate velit esse, quam nihil\n      molestiae consequatur, vel illum, qui dolorem eum fugiat, quo\n      voluptas nulla pariatur?\n</table>\n"
  },
  {
    "path": "test/floats-worm.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html>\n  <head>\n    <title>Floats and iterators: search for \"worm\" and pay attention\n    to the order.</title>\n    <style type=\"text/css\">\n      .float1, .float2 {\n         padding: 0.5em;\n         border: 1px dashed #404040;\n         width: 30%;\n      }\n      .float1 {\n         float: left;\n         margin: 0.5em 0.5em 0.5em 0;\n      }\n      .float2 {\n         float: right;\n         margin: 0.5em 0 0.5em 0.5em;\n      }\n\n    </style>\n  </head>\n  <body>\n    <div class=\"float1\">1: apple apple worm apple apple</div>\n    <p>2: apple apple apple apple apple apple apple apple apple apple\n      apple apple apple apple apple apple apple apple apple apple\n      apple apple apple apple worm apple apple apple apple apple apple\n      apple apple apple apple apple apple apple apple apple apple\n      apple apple apple apple apple apple apple apple</p>\n    <div class=\"float2\">3: apple apple worm apple apple</div>\n    <p>4: apple apple apple apple apple apple apple apple apple apple\n      apple apple apple apple apple apple apple apple apple apple\n      apple apple apple apple worm apple apple apple apple apple apple\n      apple apple apple apple apple apple apple apple apple apple\n      apple apple apple apple apple apple apple apple</p>\n    <div class=\"float1\">5: apple apple worm apple apple</div>\n  <body>\n</html>\n\n\n\n"
  },
  {
    "path": "test/floats1.html",
    "content": "<div>First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First\nparagraph. <div style=\"float: left; border: 1px dashed black\">Left\nfloat. Left float. Left float. Left float. Left float. Left\nfloat. Left float. Left float. Left float. Left float. Left\nfloat. Left float. Left float. Left float. Left float. Left\nfloat. Left float. Left float. Left float. Left float.</div> First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph. First paragraph. First\nparagraph. First paragraph. First paragraph.</div>\n<div><div>Second paragraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph.<div style=\"float:\nright; border: 1px dashed black\">Right float. Right float. Right\nfloat. Right float. Right float. Right float.</div>  Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph. Second\nparagraph. Second paragraph. Second paragraph.</div></div>\n"
  },
  {
    "path": "test/floats2.html",
    "content": "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\naccusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae\nab illo inventore veritatis et quasi architecto beatae vitae dicta\nsunt, explicabo.\n<div style=\"float:left; border: 1px dashed black\">Some text in a\nfloat.<br /><img src=\"http://www.dillo.org/dw/html/not-so-simple-container.png\" /></div>\nnemo enim ipsam voluptatem, quia voluptas sit,\naspernatur aut odit aut fugit, sed quia consequuntur magni dolores\neos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,\nqui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit,\nsed quia non numquam eius modi tempora incidunt, ut labore et dolore\nmagnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis\nnostrum exercitationem ullam corporis suscipit laboriosam, nisi ut\naliquid ex ea commodi consequatur? quis autem vel eum iure\nreprehenderit, qui in ea voluptate velit esse, quam nihil molestiae\nconsequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla\npariatur?\n"
  },
  {
    "path": "test/floats3.html",
    "content": "<p>Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\naccusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae\nab illo inventore veritatis et quasi architecto beatae vitae dicta\nsunt, explicabo.</p>\n<div style=\"float:left; border: 1px dashed black\">Some text in a\nfloat.<br /><img src=\"http://www.dillo.org/dw/html/not-so-simple-container.png\" /></div>\n<p>nemo enim ipsam voluptatem, quia voluptas sit, aspernatur aut odit\naut fugit, sed quia consequuntur magni dolores eos, qui ratione\nvoluptatem sequi nesciunt, neque porro quisquam est, qui dolorem\nipsum, quia dolor sit, amet, consectetur, adipisci velit, sed quia non\nnumquam eius modi tempora incidunt, ut labore et dolore magnam aliquam\nquaerat voluptatem. ut enim ad minima veniam, quis nostrum\nexercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex\nea commodi consequatur? quis autem vel eum iure reprehenderit, qui in\nea voluptate velit esse, quam nihil molestiae consequatur, vel illum,\nqui dolorem eum fugiat, quo voluptas nulla pariatur?</p>\n"
  },
  {
    "path": "test/floats4.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html>\n  <head>\n    <title>Floats 4</title>\n    <style type=\"text/css\">\n      .border {\n         background-color: #e0e0ff;\n         padding: 1cm;\n      }\n      .float1, .float2 {\n         margin: 1cm;\n         padding: 1cm;\n         border: 1px dashed red;\n         background-color: #f0fff0;\n         float: right;\n      }\n      .float1 {\n         float: left;\n      }\n      .float2 {\n         float: right;\n      }\n\n      .wide {\n         margin: 1cm 0;\n         padding: 1cm;\n         border: 1px dashed red;\n         background-color: #ffffd0;\n         width: 40cm;\n      }\n    </style>\n  </head>\n  <body class=\"border\">\n    <div class=\"float2\">Some text in a float.</div>\n\n    <p>Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\n      accusantium doloremque laudantium, totam rem aperiam eaque ipsa,\n      quae ab illo inventore veritatis et quasi architecto beatae\n      vitae dicta sunt, explicabo. nemo enim ipsam voluptatem, quia\n      voluptas sit, aspernatur aut odit aut fugit, sed quia\n      consequuntur magni dolores eos, qui ratione voluptatem sequi\n      nesciunt, neque porro quisquam est, qui dolorem ipsum, quia\n      dolor sit, amet, consectetur, adipisci velit, sed quia non\n      numquam eius modi tempora incidunt, ut labore et dolore magnam\n      aliquam quaerat voluptatem. ut enim ad minima veniam, quis\n      nostrum exercitationem ullam corporis suscipit laboriosam, nisi\n      ut aliquid ex ea commodi consequatur? quis autem vel eum iure\n      reprehenderit, qui in ea voluptate velit esse, quam nihil\n      molestiae consequatur, vel illum, qui dolorem eum fugiat, quo\n      voluptas nulla pariatur?</p>\n\n    <table class=\"wide\"><tbody><tr><td>Ἐν ἀρχῇ ἦν ὁ Λόγος, καὶ ὁ Λόγος\n      ἦν πρὸς τὸν Θεόν, καὶ Θεὸς ἦν ὁ Λόγος. Οὗτος ἦν ἐν ἀρχῇ πρὸς τὸν\n      Θεόν. πάντα δι' αὐτοῦ ἐγένετο, καὶ χωρὶς αὐτοῦ ἐγένετο οὐδὲ ἕν ὃ\n      γέγονεν. ἐν αὐτῷ ζωὴ ἦν, καὶ ἡ ζωὴ ἦν τὸ φῶς τῶν ἀνθρώπων. καὶ\n      τὸ φῶς ἐν τῇ σκοτίᾳ φαίνει, καὶ ἡ σκοτία αὐτὸ οὐ\n      κατέλαβεν.</tbody></tr></td></table>\n  <body>\n</html>\n\n\n\n"
  },
  {
    "path": "test/floats5.html",
    "content": "Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\naccusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae\nab illo inventore veritatis et quasi architecto beatae vitae dicta\nsunt, explicabo.\n<div style=\"float:left; border: 1px dashed black\"><img src=\"http://www.dillo.org/Icons/ProgramIcon16.png\" /></div>\nnemo enim ipsam voluptatem, quia voluptas sit,\naspernatur aut odit aut fugit, sed quia consequuntur magni dolores\neos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,\nqui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit,\nsed quia non numquam eius modi tempora incidunt, ut labore et dolore\nmagnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis\nnostrum exercitationem ullam corporis suscipit laboriosam, nisi ut\naliquid ex ea commodi consequatur? quis autem vel eum iure\nreprehenderit, qui in ea voluptate velit esse, quam nihil molestiae\nconsequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla\npariatur?\n"
  },
  {
    "path": "test/form.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"form.hh\"\n\nnamespace form {\n\nusing namespace dw::core::ui;\n\nForm::ResourceDecorator::ResourceDecorator (const char *name)\n{\n   this->name = strdup (name);\n}\n\nForm::ResourceDecorator::~ResourceDecorator ()\n{\n   free((char *)name);\n}\n\nForm::TextResourceDecorator::TextResourceDecorator (const char *name,\n                                                  TextResource *resource):\n   Form::ResourceDecorator (name)\n{\n   this->resource = resource;\n}\n\nconst char *Form::TextResourceDecorator::getValue ()\n{\n   return resource->getText ();\n}\n\nForm::RadioButtonResourceDecorator::RadioButtonResourceDecorator\n   (const char *name, RadioButtonResource *resource, const char **values):\n      Form::ResourceDecorator (name)\n{\n   this->resource = resource;\n\n   int n = 0;\n   while (values[n])\n      n++;\n   this->values = new const char*[n + 1];\n   for (int i = 0; i < n; i++)\n      this->values[i] = strdup (values[i]);\n   this->values[n] = 0;\n}\n\nForm::RadioButtonResourceDecorator::~RadioButtonResourceDecorator ()\n{\n   for (int i = 0; values[i]; i++)\n      free((char *)values[i]);\n   delete[] values;\n}\n\nconst char *Form::RadioButtonResourceDecorator::getValue ()\n{\n   RadioButtonResource::GroupIterator *it;\n   int i;\n   for (it = resource->groupIterator (), i = 0; it->hasNext (); i++) {\n      RadioButtonResource *resource = it->getNext ();\n      if(resource->isActivated ()) {\n         it->unref ();\n         return values[i];\n      }\n   }\n\n   it->unref ();\n   return NULL;\n}\n\nForm::CheckButtonResourceDecorator::CheckButtonResourceDecorator\n   (const char *name, CheckButtonResource *resource):\n   Form::ResourceDecorator (name)\n{\n   this->resource = resource;\n}\n\nconst char *Form::CheckButtonResourceDecorator::getValue ()\n{\n   return resource->isActivated () ? \"true\" : NULL;\n}\n\nForm::SelectionResourceDecorator::SelectionResourceDecorator\n   (const char *name, SelectionResource *resource, const char **values):\n      Form::ResourceDecorator (name)\n{\n   this->resource = resource;\n\n   int n = 0;\n   while (values[n])\n      n++;\n   this->values = new const char*[n + 1];\n   for(int i = 0; i < n; i++)\n      this->values[i] = strdup (values[i]);\n   this->values[n] = 0;\n}\n\nForm::SelectionResourceDecorator::~SelectionResourceDecorator ()\n{\n   for(int i = 0; values[i]; i++)\n      free((char *)values[i]);\n   delete[] values;\n}\n\nconst char *Form::SelectionResourceDecorator::getValue ()\n{\n   valueBuf.clear();\n   int n = resource->getNumberOfItems ();\n   bool first = true;\n   for (int i = 0; i < n; i++) {\n      if (resource->isSelected (i)) {\n         if (!first)\n            valueBuf.append (\", \");\n         valueBuf.append (values[i]);\n         first = false;\n      }\n   }\n\n   return valueBuf.getChars ();\n}\n\nvoid Form::FormActivateReceiver::activate (Resource *resource)\n{\n   form->send (NULL, NULL, -1, -1);\n}\n\nvoid Form::FormActivateReceiver::enter (Resource *resource)\n{\n}\n\nvoid Form::FormActivateReceiver::leave (Resource *resource)\n{\n}\n\nForm::FormClickedReceiver::FormClickedReceiver (Form *form, const char *name,\n                                          const char *value)\n{\n   this->form = form;\n   this->name = strdup (name);\n   this->value = strdup (value);\n}\n\nForm::FormClickedReceiver::~FormClickedReceiver ()\n{\n   free((char *)name);\n   free((char *)value);\n}\n\nvoid Form::FormClickedReceiver::clicked (Resource *resource,\n                                         dw::core::EventButton *event)\n{\n   form->send (name, value, event->xCanvas, event->yCanvas);\n}\n\nForm::Form ()\n{\n   resources = new lout::container::typed::List <ResourceDecorator> (true);\n   activateReceiver = new FormActivateReceiver (this);\n   clickedReceivers =\n      new lout::container::typed::List <FormClickedReceiver> (true);\n}\n\nForm::~Form ()\n{\n   delete resources;\n   delete activateReceiver;\n   delete clickedReceivers;\n}\n\n/**\n * \\brief Adds an instance of dw::core::ui::TextResource.\n */\nvoid Form::addTextResource (const char *name,\n                            dw::core::ui::TextResource *resource)\n{\n   resources->append (new TextResourceDecorator (name, resource));\n   resource->connectActivate (activateReceiver);\n}\n\n/**\n * \\brief Adds an instance of dw::core::ui::RadioButtonResource.\n *\n * This method has to be called only once for a group of radio buttons.\n */\nvoid Form::addRadioButtonResource (const char *name,\n                                   dw::core::ui::RadioButtonResource *resource,\n                                   const char **values)\n{\n   resources->append (new RadioButtonResourceDecorator (name, resource,\n                                                        values));\n   resource->connectActivate (activateReceiver);\n}\n\n/**\n * \\brief Adds an instance of dw::core::ui::CheckButtonResource.\n */\nvoid Form::addCheckButtonResource (const char *name,\n                                   dw::core::ui::CheckButtonResource *resource)\n{\n   resources->append (new CheckButtonResourceDecorator (name, resource));\n   resource->connectActivate (activateReceiver);\n}\n\n/**\n * \\brief Adds an instance of dw::core::ui::SelectionResource.\n */\nvoid Form::addSelectionResource (const char *name,\n                                 dw::core::ui::SelectionResource *resource,\n                                 const char **values)\n{\n   resources->append (new SelectionResourceDecorator (name, resource, values));\n   resource->connectActivate (activateReceiver);\n}\n\n/**\n * \\todo Comment this;\n */\nvoid Form::addButtonResource (const char *name,\n                              dw::core::ui::ButtonResource *resource,\n                              const char *value)\n{\n   FormClickedReceiver *receiver =\n      new FormClickedReceiver (this, name, value);\n   resource->connectClicked (receiver);\n   clickedReceivers->append (receiver);\n}\n\n/**\n * \\todo Comment this;\n */\nvoid Form::send (const char *buttonName, const char *buttonValue, int x, int y)\n{\n   for (lout::container::typed::Iterator <ResourceDecorator> it =\n           resources->iterator ();\n        it.hasNext (); ) {\n      ResourceDecorator *resource = it.getNext ();\n      const char *value = resource->getValue ();\n      if (value)\n         printf (\"%s = %s; x=%d y=%d\\n\", resource->getName (), value, x, y);\n   }\n\n   if(buttonName && buttonValue)\n      printf (\"%s = %s\\n\", buttonName, buttonValue);\n}\n\n} // namespace form\n"
  },
  {
    "path": "test/form.hh",
    "content": "#ifndef __TEST_FORM_HH__\n#define __TEST_FORM_HH__\n\n#include \"../dw/core.hh\"\n#include \"../dw/ui.hh\"\n\nnamespace form {\n\n/**\n * \\brief Handles HTML form data.\n *\n * Add resources by calling the respective add...Resource method. Furtermore,\n * this class impelements dw::core::ui::ButtonResource::ClickedReceiver, the\n * form data is printed to stdout, when the \"clicked\" signal is received.\n *\n * \\todo wrong comment\n */\nclass Form\n{\nprivate:\n   /**\n    * \\brief Decorates instances of dw::core::ui::Resource.\n    *\n    * This is the abstract base class, sub classes have to be defined to\n    * decorate specific sub interfaces of dw::core::ui::Resource.\n    */\n   class ResourceDecorator: public lout::object::Object\n   {\n   private:\n      const char *name;\n\n   protected:\n      ResourceDecorator (const char *name);\n      ~ResourceDecorator ();\n\n   public:\n      inline const char *getName () { return name; }\n      virtual const char *getValue () = 0;\n   };\n\n   /**\n    * \\brief Decorates instances of dw::core::ui::TextResource.\n    */\n   class TextResourceDecorator: public ResourceDecorator\n   {\n   private:\n      dw::core::ui::TextResource *resource;\n\n   public:\n      TextResourceDecorator (const char *name,\n                            dw::core::ui::TextResource *resource);\n      const char *getValue ();\n   };\n\n   /**\n    * \\brief Decorates instances of dw::core::ui::RadioButtonResource.\n    *\n    * This class has to be instantiated only once for a group of radio\n    * buttons.\n    */\n   class RadioButtonResourceDecorator: public ResourceDecorator\n   {\n   private:\n      dw::core::ui::RadioButtonResource *resource;\n      const char **values;\n\n   public:\n      RadioButtonResourceDecorator (const char *name,\n                                   dw::core::ui::RadioButtonResource\n                                   *resource,\n                                   const char **values);\n      ~RadioButtonResourceDecorator ();\n      const char *getValue ();\n   };\n\n   /**\n    * \\brief Decorates instances of dw::core::ui::CheckButtonResource.\n    */\n   class CheckButtonResourceDecorator: public ResourceDecorator\n   {\n   private:\n      dw::core::ui::CheckButtonResource *resource;\n\n   public:\n      CheckButtonResourceDecorator (const char *name,\n                                    dw::core::ui::CheckButtonResource\n                                    *resource);\n      const char *getValue ();\n   };\n\n   /**\n    * \\brief Decorates instances of dw::core::ui::SelectionResource.\n    */\n   class SelectionResourceDecorator: public ResourceDecorator\n   {\n   private:\n      dw::core::ui::SelectionResource *resource;\n      const char **values;\n      lout::misc::StringBuffer valueBuf;\n\n   public:\n      SelectionResourceDecorator (const char *name,\n                                  dw::core::ui::SelectionResource *resource,\n                                  const char **values);\n      ~SelectionResourceDecorator ();\n      const char *getValue ();\n   };\n\n   class FormActivateReceiver: public dw::core::ui::Resource::ActivateReceiver\n   {\n   private:\n      Form *form;\n\n   public:\n      inline FormActivateReceiver (Form *form) { this->form = form; }\n\n      void activate (dw::core::ui::Resource *resource);\n      void enter (dw::core::ui::Resource *resource);\n      void leave (dw::core::ui::Resource *resource);\n   };\n\n   class FormClickedReceiver:\n      public dw::core::ui::Resource::ClickedReceiver\n   {\n   private:\n      Form *form;\n      const char *name, *value;\n\n   public:\n      FormClickedReceiver (Form *form, const char *name, const char *value);\n      ~FormClickedReceiver ();\n\n      void clicked(dw::core::ui::Resource *resource,\n                   dw::core::EventButton *event);\n   };\n\n   lout::container::typed::List <ResourceDecorator> *resources;\n   FormActivateReceiver *activateReceiver;\n   lout::container::typed::List <FormClickedReceiver> *clickedReceivers;\n\npublic:\n   Form ();\n   ~Form ();\n\n   void addTextResource (const char *name,\n                         dw::core::ui::TextResource *resource);\n   void addRadioButtonResource (const char *name,\n                                dw::core::ui::RadioButtonResource *resource,\n                                const char **values);\n   void addCheckButtonResource (const char *name,\n                                dw::core::ui::CheckButtonResource *resource);\n   void addSelectionResource (const char *name,\n                              dw::core::ui::SelectionResource *resource,\n                              const char **values);\n   void addButtonResource (const char *name,\n                           dw::core::ui::ButtonResource *resource,\n                           const char *value);\n\n   void send (const char *buttonName, const char *buttonValue, int x, int y);\n};\n\n} // namespace form\n\n#endif // __TEST_FORM_HH__\n"
  },
  {
    "path": "test/hyph-de-1996.pat",
    "content": "% TeX-Trennmuster für die reformierte (2006) deutsche Rechtschreibung\n%\n%\n% Copyright (C) 2007, 2008, 2009, 2011, 2012 Werner Lemberg <wl@gnu.org>\n%\n% This program can be redistributed and/or modified under the terms\n% of the LaTeX Project Public License Distributed from CTAN\n% archives in directory macros/latex/base/lppl.txt; either\n% version 1 of the License, or any later version.\n%\n%\n% The word list is available from\n%\n%   http://repo.or.cz/w/wortliste.git?a=commit;h=7915938d035684993f8c363729f963eec71c85b1\n%\n% The used patgen parameters are\n%\n%   1 1 | 2 5 | 1 1 1\n%   2 2 | 2 5 | 1 2 1\n%   3 3 | 2 6 | 1 1 1\n%   4 4 | 2 6 | 1 4 1\n%   5 5 | 2 7 | 1 1 1\n%   6 6 | 2 7 | 1 6 1\n%   7 7 | 2 13 | 1 4 1\n%   8 8 | 2 13 | 1 8 1\n.ab1a\n.abi4\n.ab3l\n.abo2\n.ab3ol\n.ab1or\n.ack2\n.ag4n\n.ag4r\n.ag2u\n.ai2s\n.akt2a\n.al3br\n.al2e\n.al5l4en\n.al4tei\n.alt3s\n.ampe4\n.amt4s3\n.an3d2\n.anden6k\n.and4ri\n.ang2\n.an3gli\n.angs4\n.angst3\n.an3s\n.an4si.\n.ans2p\n.ans2t\n.an4tag\n.an3th\n.apo1\n.aps2\n.ari1e\n.ark2a\n.ar4m3ac\n.ar2sc\n.ar4t3ei\n.as3t\n.as4ta\n.at4h\n.au3d\n.au2f3\n.au4s3\n.ausch3\n.ax4\n.äm3\n.ät2s\n.be3erb\n.bei6ge.\n.be3ra\n.be3r2e\n.berg3a\n.ber6gab\n.ber4g3r\n.boge2\n.bo4s3k\n.bu4ser\n.by4t\n.ch2\n.dab4\n.da2r1\n.da4rin\n.darm1\n.da4te.\n.da4tes\n.de2al\n.de1i\n.de4in.\n.de1o2\n.de3r4en\n.de1s\n.des2e\n.de3sk\n.des2t\n.dien4e\n.do2mo\n.do1pe\n.dorf1\n.dü1b\n.dys1\n.ebe2r1\n.ehe1i\n.ei3e2\n.ei4na\n.einen6g\n.ei2sp\n.ei4st\n.ei4tr\n.eke2\n.el2bi\n.elb3s\n.em3m2\n.en1\n.en4d3er\n.en5der.\n.en2d3r\n.end3s\n.enn2\n.enns3\n.en2t3\n.en4tei\n.en4tr\n.er8brecht\n.er2da\n.er4dan\n.er4dar\n.er4dei\n.er4der\n.er1e\n.ere3c\n.erf4\n.er1i\n.er8stein\n.er8stritt.\n.er8stritten.\n.er4zen4\n.es1p\n.es3ta\n.es5t4e\n.est2h\n.es3to\n.es5tr\n.et2s\n.eu1\n.eu3g4\n.eu3t\n.eve4r\n.ext4\n.fe2i\n.fer4no\n.fi3est\n.fi4le.\n.fi4len\n.fi2s\n.flug1\n.for2t\n.fs4\n.fu2sc\n.ga4t\n.gd2\n.ge5nar\n.ge3ne\n.ge3r2a\n.ge3r2e\n.ge3u\n.gs4\n.guss1\n.hau2t1\n.he2\n.he3fe\n.her3an\n.he3ri\n.he6r5inn\n.ho4met\n.ia4\n.im2a\n.ima4ge\n.im5m\n.in1\n.in3e\n.ink4\n.inn2e\n.int6\n.inu1\n.ire3\n.is2a\n.jor3\n.ka2b5l\n.ka2i\n.kamp2\n.ka4t3io\n.ki4e\n.kle2i\n.kopf1\n.ks2\n.kus2\n.le4ar\n.li2f\n.li4tu\n.lo4g3in\n.lo3ver\n.lus4tr\n.ma3d\n.ma2i\n.ma3la\n.ma2st\n.md2\n.me2e\n.mel2a\n.men8schl\n.men8schw\n.men3t4\n.mi4t1\n.mm2\n.näs1c\n.ne4s\n.ni4e\n.nob4\n.no4th\n.nus2\n.oa3\n.ob1a\n.obe2\n.oper4\n.or2a\n.ort2\n.orts3e\n.oste2\n.ost5end\n.os8ten8de\n.oste6re\n.ost3r\n.ozo4\n.öd2\n.pa4r1e\n.par3t4h\n.pf4\n.ph4\n.poka2\n.pro1\n.ps2\n.ram3s\n.reb3s2\n.re3cha\n.rein4t\n.reli1\n.reli3e\n.res6tr\n.ri2as\n.richt6e\n.ro4a\n.ro3m2a\n.rö2s1\n.rü1b\n.rü6cker6\n.sali3e\n.sch4\n.se3ck\n.sen3s\n.ser2u\n.se2t1\n.sha2\n.sim3p4\n.si4te\n.ski1e\n.spiege8lei\n.st4\n.sto4re\n.sucher6\n.tage4s\n.tal2e\n.tan4k3l\n.ta2to\n.te2e\n.te2f\n.te3no\n.te2s\n.te4st\n.th4\n.ti2a\n.tid1\n.ti2s\n.ti5ta\n.tite4\n.to4nin\n.to4pl\n.to2w\n.tri3es\n.tro2s\n.ts2\n.tu3ri\n.uf2e2\n.ufer1\n.ul4mei\n.um3\n.umo2\n.un3a2\n.un3d\n.un3e\n.un3g\n.uni4t\n.un3s\n.uns4t\n.ur1\n.ur2i\n.urin4s\n.ur3o2m\n.uro2p\n.ur3s2\n.ut2a\n.ut3r\n.übe4\n.ve5n2e\n.vo4r\n.wah4l\n.wa2s\n.wei4ta\n.wi4e\n.wor2\n.wort5en6\n.wor8tend\n.wor4tu\n.xe3\n.ya4l\n.za2s\n.zi2e\n.zin4st\n.zwe2\n2aa\na1ab\naa2be\naa1c\naa2gr\n4a1a2n\n4a2ar\naa2r1a\naar3f4\naart4\naas5t\naat4s3\na3au\na1ä\na1b\n2aba\nab1auf\nab1ä\nab2äu\n1abd\nab1eb\nabe1e\nabei1\nab1eil\n2abel\nabe2la\na3ber\nab1erk\nab1err\nab1erz\nab3esse\n2abet\n2abew\n1abf\n3abfi\n1abg\n1abh\n2abi\nab1ins\nab1ir\nab1it\n1abk\nab1l\n1a2bla\nab5lag\n1a2blä\n2able\nab4le.\nab3li\nab4lo\n3a2blö\na2blu\n1abn\na2bo.\nab2of\na2bon\n2abor\nab3r\na3bra\na4brä\n2abrü\n1abs\n2abs.\nabs2a\n2absar\nab3s2i\nab3sp\nabst4\n2abst.\nab3ste\nab3sz\n1abtei\n2abu\nab1ur\n2abü\n1abw\n2aby\naby4t\n1abz\n2ac.\n2aca\n2ac1c\na1cem\n2ach.\nach1a\na1chal\nach3au\n2achb\na1che\na2ch1e2c\nach1ei\na4cherf\na4cherk\na4cherö\na4ch3erw\n4achf\na1chi\nach3l\nach3m\nach3n\na1cho\na3cho.\nach1o2b\nach1or\nach3ö\nach3r\nach3su\na4cht\nacht5erg\nach2t1o\nach8traum\nach8träume.\nach8träumen.\nach6trit\na1chu\nach1u2f\nach3ü\n2achv\n4ach1w\na1ci\nac1in\na1ckar\nack2en\na2ckin\nack2se\nack3sl\nack3sta4\na1cl\nacon4n\n2acu\na1ç\na1d\n2ada.\na3d2ab\nad2ag\nad3ama\na2d1an\n3a4dap\na3d2ar3\n4adav\n1a2dä\nad1c\n1add\n2ade.\nade2al\nadefi4\na2dein\n2aden\nade1r2a\na2deri\n4ade1s\nade3s2p\nades4s\nade5str\n2adf\n2adh\n4a3di\nadi3en\n5adj\n2ado\nad2ob\n2adp\n2adq\n2ad3rec\nad4res\nad3ru\n2ads2\nad3st\nad3sz\n2ad2t1\nad4te\nad4tr\n2adu\n2a1e\nae2b\nae2c\nae2d\na2ek\na2ela\na2ele\nae2o3\nae2p\n3a2er2o1\naes5t\na2et\na2ew\nae2x\naf1a\na2fak\na2fan\na3far\naf4at\na2fau\n2afe\na2f1ec\na2fent\naf1erl\na2fex\naf2fl\naf4flu\n2afi\n2af3l\nafo1s\na2fö\naf3ra\naf3rä\naf3re\naf3rö\naf3s2a\naf2sp\naf2t1a\naf2tei\naf4t3erl\naf2t3r\naf4t5re\naf2tur\na2f3ur\na1g\n2aga\nag1ab\nag1a2d\nag1ar\nag1au\nag2di\nag2dr\nag2du\nage1i\nage4na\nage4neb\na2gent\na4gentu\nage4ral\n2ages\nage2sa\nage4sel\nage4si\nage2s3p\nag3esse\nage4s3ti\nag3gl\n1aggr\n3a2git\n2a2gl\nag4la\na4glö\nag2n\nag4ne.\nag4nu\na2g3re\na2g3ri\nag4ro\nagsa2\nag4sam\nag4set\nags3p\nag4spo\nag3sta\nag3ste\nags4toc\n2agt\nag2th\na2gund\n2ah.\n2a1ha\nah4at\n2a1he\na2h1erh\nahe1s\na1h2i\nahin3\nahl3a2\nah4l1ei\nah4l3erh\nah2lö\nahl3sz\nah4n1a\nahner4e\nahnt2\n1ahor\nah1os\na2h3ö\nahr1a\nah3r2e\nahre4s3\nah3ri\nahrta4\nahr6tri\n2ahs\naht3s\na1hu\nah1w\na1hy\n2ai\naian3\naid2s\nai1e2\naien3\naif2\nai3g4\na3ik.\nai3ke\nai3ku\na2il\nai2lo\na1ind\nain4e\na1ing\nain3sp\nai2sa\na3isch.\nai3s2e\naiso2\na3iv.\naive3\na3ivl\na3ivs\na1j\naje2\n2ak.\n1a2k4ad\n2akal\n2a3kam\n2akar\nak4at\n1a2kaz\n2akb\n2akc\n2akd\n4a1ke\na2kef\naken2n\na2keu\n2a1ki\n2ak3l\nak4li\n4ako\n2a1kr\n4akra\nak3rau\n3akro\n2aks\nak3sh\n2akta\nak5tan\n2aktb\n2aktik\nak2t3r\nak5t4ri\n2aktst\n2a1ku\na2kun\n4a3kü\n1akz\na1la\n2ala.\nal1ab\nala5ch2\nal1af\nala2g\nal1age\na3lal\nal1am\nal3ame\nalami5\nal3amp\nal1ana\na2l1ang\nal1ans\nal1anz\na2lar\na3lar.\na3lare\nal2arm\nal3arr\nala4s\nal1asi\nal1ass\n2alat\nal1au\nal3aug\na1lä\nal1äm\nalb3ein\nalb3eis\nal4berh\nal4b3erw\nal2b1l\nalb3li\nal2boh\nal2br\nalb3ru\nalb3s\nal2dä\nal2dr\nalds2t\nal3du\n2ale\n3a2l1e2b\n3a2l1ef\na4l1eh\na2l1ei\na4l3ein\na2l1el\nalen1\nal3ends\na2leng\nale2p\nal1epo\na2l1erf\na2l1erh\nal1erl\n3alerm\na2l1ert\n3a2lerz\na2l1esk\nale4t\nal1eta\nal1eth\na2l1eu\na4leur\n3a2lex\nalf4r\n3algi\nal2gli\n2ali\nali4ene\nali4nal\nal1ins\na2linv\nalk1ar\n1alkoh\nalk3s2\nalks4t\nal2lab\nal2l3a4r\nal2lau\nal3lend\nall5erfa\nal3les\nalli5er.\nalli7ers.\nal2lob\n3almb\n2alo\na2l1o2b\nalo2ga\nal1ope\nal1orc\na2l1ö\nal2ös\n3alpe.\n1alph\nal3skl\nal4spal\nal5s6terb\nal3sun\nal2tak\nal3tam\nalt3eig\nal4t3erf\nal2tre\nal2tri\nalt3ric\nal2tro\nalt2se\nalt4stü\na1lu\nal2uf\na2lum\nal1umb\nal1ur\n4aly\nalzer4z\nal2zw\n2am.\n2am2a\namab4\namad2\nama3g\n2amä\n2ambiq\n2am4e\n4ame.\na2meb\name2n1\namer2a\na2meri\name3ru\na4mesh\na3met\na2mew\n2amf\na3mi.\na3mie\n2a3mir\na3mis\nami3ta\nami3ti\n2amk\n2aml\n2ammal\nam2mei\nam2min\n2amml\nammu2\na2mö\namp2fa2\nam3pr\n2am2s\nam3sa\nam4schl\nam3str\n1amt.\nam2t1a\nam2t1ä\nam4tel\n2amtem\nam4t3ern\nam4tö\nam2t3r\nam4tre\nam2tu\n2amu\n2ana.\n2anab\nana3c\nanadi3\na3nak\nan1alg\nana4lin\n2anam\n2anan\n2ana1s4\nan1äs\n1anb\n2anbu\nan3ch\n2and.\nan3dac\nand4art\nandel4s\nande2s\nan2dex\nan2d3rü\nand4sas\nand6spas\nand3ste\nand2su\n2andu\nand1ur\n2ane\nan3e2c\na3nee\nan2ei.\nan3eif\nan1e4k\n3a4n1erb\nan1eth\n1anf\n2anfi\nanft5s\nan3f2u\n4ang.\n3angeb\nan2g1ei\nan4g3erf\nan4g3erl\nan4g3erz\n2angf\n2angh\n2angie\nang1l\nan2gla\n2ango\nang1r\nan2g3ra\n4angs.\nang4s3po\n1anh\n2a3ni\nan2id\nani5ers.\n3a4nim\na4nins\n2anj\n2ank.\nan2k1an\nan2kei\nan3kl\nan4klö\nan2k3no\nank1r\nan2k3ra\nan2k3rä\nankt4\nan2ky\n1anl\n2anmu\n2ann\n3an3na\nann2ab\n3annä\nan3n2e\nan1od\na3nol\na2n1or\na3nos\na1nö\n2anpr\n1anr\n1ansä\n1ansc\nans2en\nan2seu\n2ansk\nan3skr\nans1pa\n1anspr\nan3s2z\n2ant.\nan2t3a4r\n1antá\n1antei\n3antenn\nan3t4he\n1anthr\n2anto\n1antr\nant3rin\nan2tro\n1antw\n2a1nu\nanu3s\na1nü\n1anw\n2anwet\n2anzb\n1anzei\n2anzg\nan2z1i4n\n2anzs\n1anzü\n2anzw\nan2zwi\n2ao\nao1i\na1op\na1or\na1os3\nao3t2\na3ot.\na1ö\na1p\n2ap.\n2apa\n2ape\na2pef\na3pel\na2pé\na2pf\nap2fa\na3pfl\na3phä\na2pht\n2ap3l\nap2n\na2pot\n3appl\nap3pu\n2apr\n2a3pu\n2aq\n2ar.\na1ra\na3ra.\nar2ab\nar3abt\nara3d2\na2r3al\na3ra3li\na2r1ang\na2r1ans\na2r1anz\na2r3app\n2a2rar\na2r1au\na1rä\n1arb\n2arb.\n4arba\nar2bau\nar2bec\n2arben\n2arbi\nar2bl\n2arbr\nar2bre\n2arbs2\n2arbt\n2arbu\nar2b3un\n1ar1c\nar2dro\n2are\na2rea\nar1eff\na4reg\na2reh\nar1ehr\na2rein\na4rek\na3ren\naren4se\nare3r2a\nar2erf\na2r1erh\na2reri\na2rerl\nare3u\nar2ew\n2arf\narf1r\nar2f3ra\nar2gl\nar2gn\n2arh\n2ari\nar2ia\nari3e4n\nari3erd\nari3erg\nar1im\narin3it\nar1int\na3riu\nar2kal\nark3amt\nar2k1ar\nark3aue\nark3lag\nar2kor\nar4kri\nark1s4\nark3sa\nark3sh\nark4tre\nar2les\narm2ä\nar4merk\nar3m2or\nar2nan\narn2e\n2a1ro\nar1ob\na2r1o2d\na2r1op\na2ror\n2arr\nar2r3ad\narre4n3\nar2rh\narr3he\n2arsa\nar4schl\narse3\nar3s2h\n2arsi\nar2st\nar3sta\nar3t2e\nar2the\nar3t2i\nartin2\n2arto\nart3r\nar4tram\nar6tri\n2arts\n2aru\nar1uh\nar1um\na2rü\n2arv\narwa2\n2ary\nar2zä\n2arze\n1arzt\nar2z1w\nas1ala\nas3au\na2s1ä\na2sca\na3sche\na4schec\na3schi\nasch3la\na2schm\na3schu\n4as2e\na2seb\na2s3e2m\na3ses\n4ash\na3s2hi\nasin2g\n2asis\naska3s\na3skop\na2s1o2f\nas1or\na2sö\na2s1p\nas3pan\nas2ph\nas2pi\nas2po\na3spu\nas3s2a\nas3s2e\nas4s3ei\nas3s2i\nas2s1p\nas2st\nass3ti\nas3str\nas3stu\n2as3ta\na1s4tas\nas4tau\nas3te\nas2th\nas3ti\nas3to\nas4tof\n2astr\nast3rä\nas6t3re\na2sü\naswa2s\n3a2syl\na1ß\naße2\naßen3\n2a1t\nata1\nat1ab\nat2af\nat4ag\na2t1akt\nata3l\na3tam\nat1apf\nat1au\na2taus\na2t1ä\nat2c\nat2e\n4ate.\na2teb\nat3eig\na2teli\n4aten\na2tep\nater3s2\nate2ru\n4ates\nat2h\nat3ha\n4athe1\n3athl\n4a3ti\natil4s\nati2st\n3atm\n4atmus\nato4man\n4ator\na2t1ort\nat1ö\n4atr\natra4t\nat3rä\nat3re\nat3rom\nat2sa\nat4schn\nat2se\nat4set\nat2si\nat2so\nat2s1p\nat3ta\nat4tak\natt3ang\nat4tau\nat2tei\nat3t4hä\nat2t3rä\natt3s\na3tub\natu2n\na3tü\natz1er\nat4zerk\nat4zerw\nat2z1in\nat2zo\natz3t2\nat2z1w\na2u\n2au.\n2au1a2\n2aub\nau2bli\nau2blo\n4auc\nauch3ta\nau2dr\n2aue\naue2b\nau5erein\naue2s\nau2fa\nauf1an\n2aufe.\n2aufeh\nauf1er\nau4ferk\nauff4\n3aufn\n2aufs.\n2aug\n4augeh\n4au1i\nau2is\n2auj\naule2s\nau3lü\n4aum\nau2mal\nau2m1o\naum3p2\naum3s6\n4aun\nau3n4a\naun2e\nau2nio\nau1nu\na4unz\nau1o\n2aup2\naup4ter\n2au3r2\nau2s1ah\nausan8ne.\nau2sau\n2ausc\nau4schm\nau4scho\n1ausd\naus3erp\nau4s3erw\n3ausf\n1ausg\n1ausl\nau2so\nau2spr\n1ausr\naus3s2\n3aussag\naus4se.\nauster6m\naus5tri\n1ausü\n1ausz\n2aut.\nau2t1äu\n2aute\nau4ten4g\nau4t3erh\n1auto\n2auts4\n2auu\n2auw\n2aux\n2auz\nauz2w\n2a1ü\n2a1v\na3v4a\nava3t4\n4avi\na2vr\n2a1w\nawi3e\na1x\nax4am\nax2e\n2a1ya\na1yeu\nays4\naysi1\nay3t\n2a1z\naz2a\naz2o\naz2u\nä1a\nä1b\nä2b3l\näb2s\nä1che\näche1e\nä1chi\näch3l\nä2chr\näch2sp\näch4st\nä1chu\nä1ck\näck2e\nä1d\nä2da\nä2d1ia\nä2dr\näd2s\n2ä1e\näf2fl\näf3l\näf3r\näf2s\näft4s3\nä1g\näge1i\näge3s\nä2g3l\näg2n\nä2g3r\näg4ra\näg3str\n1ä2gy\näh1a\n2ä3he\nä3hi\nähl1a\nähl2e\näh4l3e4be\n2ähm\näh3ne\näh3ri\n2ähs\n2äh3t4\nä1hu\näh1w\nä1im\nä1is.\nä3isch.\nä1isk\nä1j\nä1k\nä2k3l\nä2k3r\nä1la\nälbe2\näl2bl\n2äle\näl2l1a\näl2p3\näl4schl\nä1lu\nämi3en\n2äml\näm2s\nämt2e\n2än.\nän5de\nän2dr\n2äne\näne2n1\näne1s\nän2f5\n2änge\nän2gl\nän2gr\näng3se\n2ä3ni\nänk2e\nän2k3l\nän2kr\nänk2s\nän3n4e2\n2äns\nän2s1c\nänse3h\nä1on\nä1pa\näp2pl\näp2pr\näp2s1c\näp4st\n1äq\nä2r3a2\när4af\när1ä\när1c\n4äre\nä2r1ei\näre2n\nä2r1ene\när2gr\när1int\när2k3l\närk2s\när4ment\närm2s\när1o2\nä1rö\närse2\när4si\när2st\närt4e\när2th\närt2s3\nä2rü\n1ärz\när2zw\nä5s4e\näse3g2\näser4ei\näse4ren\näser2i\näse3t\näskop2\näskopf3\nä3s2kr\nä2s1p\näs6s1c\näss2e\näs4s3erk\näs2st\nä4s3t2\näs4tr\nä3su\nä1ß\näß1erk\nä4t1a2\nä3te\nät2e1i\nätein2\näte2n\nät2h\nät1ob\nä2t3r\nät2sa\nät2sä\nät4schl\nät4schr\nät2s1i\näts3l\nät2s1p\nät2s3t\nät4tr\nät2zw\näu2br\näu1c\näude3\näu3el\nä2uf\näuf2e\n1äug\näug3l\n4äul\n2äum\näu2ma\näum4s5\nä2un\näun2e\näu1nu\n2äur\n2ä3us.\näu4schm\näu3se\nä3usg\nä3usk\nä3usn\näu2sp\näus2s1c\n1äuß\näu2tr\n4ä1v\n1äx\nä1z\nâ1t\ná1n\nba2bl\n2babs\nbach7t4e\nbacks4\nb1a2dr\n2b1af\n3bah\nbah2nu\nbais2\nba2ka\nba2k1er\nba2k1i\nbak1l\nbak1r\nba2kra\n3bal\nbal2a\nbal4l3eh\nbal6lerg\nbal3th\n2b1am\nban2a\n3b2and\nban2dr\nba3n2e\nb1ang\nban2k1a\nban4kl\nban2kr\n2banl\n2b1ans\nban3t\nb1anz\nbar3b\nbar3de\nba2rei\nbar2en\nbar3n\nbar3zw\n3bas\nba3s2a\nba2sc\nba2st\nbau3g\nbau1s\nbau3s2k\nbau3sp\nba1yo\n3b2ä1c\nb2är\nb2äs\n4b1b\nb3be\nbben3\nbbens2\nbbe4p\nbb3ler\nbb2lö\nbbru2c\nbb2s\nbbu1\n2b1c\n2b3d4\nbde1s\n3be.\n3bea\nbe3an\nbe3ar\nbe3as\n3beb\nb2ebe\n1be1c\nbe2del\nbedi4\nbe1eh\nbe2erk\nbe1erl\nbe1eta\n3bef4\nbe3g2\n2b1eier\nbei1f4\nbeik4\nbeil2\nbei3la\n2b1eime\nb2ein\nbe1ind\nbe1in2h\nbei3sc\nbeis2e\nbei1st\nbeit2s\n3bek\n3bel\nbe3las\nbe3lec\nbe3lei\nbe2l1en\nbe2let\nbe3li\nbel3la\nbel3sz\nbel3t4\n1bem\n1ben.\nben3ar\nben3dor\nbe3nei\n3ben3g\nbe3n2i\nben3n\nben2se\nben4spa\nben4spr\nbenst4\nben2su\n2bentb\nb2enti\nben5t4r\nb1ents\n2bentw\nben3un\nben3z2\nbe1o\nbe1ra\nber3am\nbe2ran\nber4ei.\nbe4r3eiw\nbe4rerk\nbere4s\nber6gan.\nber4in.\nber3iss\nber3na\nb1ernt\nbe2rob\nbe3rop\nber3st4a\nbe3rum\n3be1s\nbes2a\nbe2s1er\nbe3slo\nbes2po\nbess4e\nb3esst.\nbes3sz\nbe6stein\nbe4s3tol\nbe3s4ze\n3bet\nbe2tap\nbe3tha\nbe1ur\n3b2ew\n2b1ex\n1bez\n2b5f4\nbfal2\n2b1g2\nbge3\nbges4\n2b5h2\nbhut2\n1bi\nbi3ak\nbib2\nbibe2\nbien3s\nbie2s\nbik2a\nbi2ke.\nbi2kes\n3bil\nbil2a\nbi2lau\n4b1illu\nbi2lu\n2b1inb\nbin2e\n2b1inf\nbin3gl\n2b1int\nbi2o1\nbio3d\nbi3on\nbiri1\nbi3se\nb1iso\nbi2sol\nbi2sp\nbis2s1c\nbi2s5t\nb2it.\nb2it2a\nb2ite\nbi2tu\nb2i3tus\nbiz2\n4b1j\nbjek4to\n2b1k4\nbl2\n2bl.\nbla3b4\nb3lad\nb2lanc\n3blat\nb2latt\n2b3law\nb2le\n3ble2a\nb3leb\n2b3leg\n2b3leid\nb3lein\n3blem\n3blen\nb3lese\nble3sz\nb4let\nb3leu\n2blich\n3blick\nb2lie\n2blig\nbling4\nb3lis\nb2lit\n3blitz\nb2lo\nb4loc\nb3los\n2blun\n3blut\n3blü\n2b1m\n4b3n2\nbni2\nbnis1\nbo4a\nbo5as\nb1ob3\nbo2bl\nbo2br\nbo2c\nbo3ch2\nbo3d2\nboe1\nbo2e3i\n2b1of\nbo3fe\nbo1is\nbo2l1an\n3bon.\nbond1\nbon2de\nbo2ne\n3bons\nb1op\nbo1r2a\nbo4rä\nbor2d1i\nbor2d3r\nbo2rei\nbo4rig\nbor2s\nb1ort\nbor2t3r\nbo2sc\nbo4s3p\nbote3n4e\nbo3th\nbot2st\nbo2xi\nbö2b3\n2böf\n2b1p2\nbpa2g\n2b1q\nb2r4\n2br.\nb4ra.\n2b3rad\nb4rah\nb4ra3k\nbra1st4\n3brä\nbrä4u\n2bre.\n3brea\n6b5rechte\n2b3ref\n2breg\nb3reif\n3brem\n2b3rep\nb4rer\n2b3riem\nbri2er\n2brig\nb4rio\nb3roh\n2b3rol\nb4ron\nb4ruc\nbru4s\nbrust3\nbru2th\n3brü\n4b1s\nb2s1ad\nb3sand\nbs3ar\nbsat2\nb3sä\nb4sär\nbs2äu\nb5sc\nbs2ca\nb6schan\nb6schef\nbs4cu\nb3se.\nbse2b\nb3sel.\nbse2n1\nb4s1erf\nbs3e4r3in\nb4s1ers\nb3s2es\nbsi4t\nbs2ku\nb4sl\nb2s1of\nbso2r\nb2sö\nbs2pl\nb3s2pu\nbss2\nbs2t\nbst1a2b\nbst3ac\nbst1ak\nbs3tät\nbst3er\nb2stip\nb3sto\nb4stod\nb3stö\nb2s3trä\nbs3treu\nbs4tri\nb3stü\nb4stüb\nb2s1un\n4b3t\nbtal3\nbtast3r\nb5te\nb4th\nbtil4\nbt4r\nb4ts2\nbtü1\nbu2chi\nbu2e3\nbu2f\nbu3li\nbul2la\n2b3umk\nbung4\nb2urg\nbu3r4i\nbu2sa\nbu4s3cha\nbu4schl\nbu4schm\nbu4schw\nbus1er\nbu2sin\nbu2s1p\nbu2s1u\nbü1c\nbügel3e\n2b1v\n2b1w\n3by1\nby3p\nbys2\n2b3z2\nbzeit1\n1ca\n2c1ab\nca1ch\nca2e3\nca3g4\nca1h\ncal3t\n3cam\nc4an\nca2pe\n3car\ncar3n\ncarri1\nca3s2a3\ncas3t\nca3t4h\nca1y2\ncä3\ncäs2\n2cc\nc1ce\nc1ch2\nc2d2\nc3do\n2cec\nce2dr\n2cef\nce1i\n2cek\n1cen\ncen3g\n1cer\ncere3\nce3sh\n1cet\n2ceta\nce1u\n1cé\n2c1f\nc4h\n4ch.\n2chab\nch3a2bi\ncha2ck\n2chaf\n2ch1ak\nch2anb\n3chanc\nch1ang\nch3anst\n4chanz\n1chao\n4char.\n1chara\n3charta\ncha2sc\n3chato\n4chatu\nch1ärm\nch1äs\n1châ\n2chb\n2chc\n2chd\nch3e4ben\n1chef\n3chef.\nche4fer\n3chefi\n3chefs\n4chei\nch1eim\n4chelem\nche4ler\n4chents\n4chentw\ncher3a\nche3rei\n6chergeb\ncher6zie\nch1ess\n2cheta\n2ch1e4x\n1ché\n2chf\n2chg\n2chh\n1ch1ia\n2chic\nchi3na\n4chind\n3chines\n2chinf\n2chinh\nch1ins\nch1int\n2ch1inv\n1chiru\n2chj\n2chk\n2chl2\nch2le\nch3lein\nch2lu\n4ch2m\n2chn4\nchner8ei.\n2chob\ncho2f\nch1off\nch1oh\nch1orc\n2chp\nch2r4\n4chre\nchre3s\nch3rh\n1chron\n4chs\n2cht\n2chuf\n2chuh\n2chum\n2ch1unf\n2chunt\n4chü\n2chv\n4chw\n2chz\nci1c\nci2s\nc1j\nc4k\n4ck.\nck1a\n1cka.\n2ckac\n1ckag\n2ckal\n2ck3an\ncka4r1\n2ckau\nck1ä\n2ckb\n2ckc\n2ckd\n1cke\n4ckeff\n2ckeh\nck1ehe\n4ck1ei\n4ckense\nck1ent\n4ckentw\ncke2ra\nck2ere\n6ckergeb\nck1erh\n4ckerhö\n4ckerke\nck2ern\n2ckero\n2ck1err\n4ckerze\n2ck1ese\n2ckex\n2ckf\n2ckg\n2ckh\n1cki\n2ck1id\nck1im\nck1in\n3ckis\n2ckk\n2ck3l\n2ckm\n2ck3n\nck1o2\n2ckp\n2ck3r\n4cks\nck4stro\n2ckt\nckt2e\n1cku\n2ck1um3\n2ckunt\n2ck1up\n2ckv\n2ckw\n1cky\n2ckz\nc4l2\nclet4\nclo1\n1clu\nc2m2\n3co\nco2c\nco3ch\nco2d2\nco4der.\nco3di\ncoff4\ncoi2\nco1it\nco2ke\nco2le\ncol2o\ncom4te.\ncomtes4\ncon2ne\nco2pe\nco1ra\ncor3d\nco3re\ncos4\nco4te\ncô4\n2cp\n2c1q\n1c4r2\ncre2\ncre4mes\ncry2\n2cs\ncs2a\nc2si\nc1s4tr\n4c1t\ncte3e\ncti2\ncti4o\nctur6\n3cu\ncu2p3\ncussi4\n1cy\n2c1z\n3da.\nda1a\n2d1ab\n3d2abä\nda2ben\n3d2abl\nda2bre\ndab4rü\n2d1ac\ndach3a\nda2cho\ndach1s\n4d3achse\nd1af\nd1ag\ndagi2\ndah3l\nda1ho\n3d4ai\nda1in\nda1is\ndal2a\n2d1alar\ndal3b2\nda3lö\nd1alt\nd1amma\n2d1ammä\ndamo3\nd4amp\ndampf8erf\n2d1amt\nd2an.\n2d1ana\ndan4ce.\n2d1an3d2\nd1ang\n2dange\ndan4kl\ndan5kla\ndan2k1o\ndan2kr\n2d1ans\n2dantw\n2danw\nd2anz.\n4danzi\n2d1ap\nd2aph\n4dapp\nda2r3a\n2darb2\n3d2arl\ndar2ma\ndar2m1i\nda2ro\nd3arr\nd2ar3s\nd1art\nda2ru\nd2arw\nda1s\nda3s2h\ndas4t\ndat2a\ndat4e2\nda3tei\ndate4n\n4d3atl\n4datm\ndau3e\n2d1au2f\n2dauk\n2d1aus3\n4daush\n2d1äh\n2d1ämt\n2d1änd\n2d1äng\n2d1äp\n2därz\ndä2u\ndä3us\n2d1b4\ndbu2c\n2dc\nd1ch\ndco4r\n2d1d2\nddar2\nd3dh\nd5do\n1de\nde2ad\nde3as\nde3a2t\nde3b4\n2d1e4ben\n3de1c\nde4ca.\nde2cka\ndeco3\nde1e4\n2d1eff\ndeg2\nde3gl\ndehe2\nde3ho\n2d1ehr\nd1ei\nd2eic\n3d2e1im\ndein2d\ndein2s\nde2l1a4g\nde4l3aug\ndel1än\ndel1ec\ndelei4g\nde3lein\n2delek\n2delem\n2delfm\ndelle2\ndel4leb\ndel4lei\nde2l1ob\nde2lop\nde3lor\nde2lö\ndel4san\ndel5sc\ndel2s5e\ndel2so\ndel2s1p\ndel5ster\ndel3t4\ndem2ar\n2d1emp\nd2en.\ndend2\nde4n3end\n4denerg\nden3g\nd2enh\nde2ni\nden4k3li\n4den4sem\nden4sen\nden6s5tau\nden3th\n2dentw\nde1nu\nde1on\ndepi2\nd4er.\ndera2b\nde1rad\nde2rap\nder2bl\n2derdb\nde2re2b\nde4reck\nder3edi\nderer3\nde3r4erb\nde3r4erf\nde4r3ero\nderer4t\n4d3erhöh\n3derie\nderin4f\n4derklä\nder3m2\n4derneu\n4d3ersat\nder3tau\nder6t5en6d\nde3ru\nde4ruh\nde4rum\ndes1\nde2sa\nde3sac\ndesa4g\nde4sam\ndes2äc\nde2seb\nde4seh\nde2sei\ndes3elt\nde2sen1\nde4set\nde2sin\nde2sor\nde2sp\ndes3s2\ndest5alt\nde2sto\ndest5rat\nde4stre\ndes4tum\nde2su\ndet2\ndeten4t\n2d1etw\nde1un\nde1url\nde3us\nde2xis\n2dexp\n2d1f4\n2d1g2\ndga2\nd2ge.\ndge4t1e\nd3gl\n2d1h2\ndha1s4\nd2his\n1di\ndi4ab\ndi2ad\ndi4am\n3dic\ndi1ce\ndi2e\ndi3e2d\ndie4neb\ndi3eni\ndi3ens.\ndie2s3c\ndiet3\ndie2th\ndige4s\ndik2a\ndil2s5\n2d1imb\ndin2a\n2d1ind\n2d1inf\n2d1inh\n2d1in1it\n4d3inner\n2d1ins\n2d1int\ndi2ob\ndion3s\ndi1p\ndi4re.\ndi2ren\ndi2ris\n2d1irl\ndi2sp\n2d1isr\ndist2\ndi2s5te\ndi2ta\ndi4teng\ndi4t3erl\ndi4t3erm\ndi4t3ers\ndi2th\ndi4t3r\ndit3s\ndi2tu\ndi5v\ndi3z2\n2d1j\n2d1k4\n4d1l2\nd3la\nd3le\ndle2ra\ndli2f\ndl3m\ndl3s\n2d3m2\n4d5n2\ndni2\ndnis1\nd1ob\nd2oba\n2dobe\ndob4l\nd2obr\n2d1o2f\ndole4\ndoll2\ndo2mar\ndo5n4a\ndoni1e\ndo2o\n2dope\n2d1opf\nd2opp\nd2o3r4a\n2dorc\n2dord\ndor2f1a\ndor2fä\ndor2fl\ndor2fr\n2d1org\ndo2rie\nd2orp\n2dort\ndor2ta\nd2os.\ndos3s\ndost1r\ndot6h\ndo3un\nd1ö\ndö2l1\n3d2ör\ndö2s1c\n2d3p2\n2d1q\nd2r4\n3d4ra.\n2d3rad\ndrag4\n2drahm\nd3rai\n3d4ram\nd3rand\n2d3rast\n2d3rauc\n2dräd\nd4räh\n2d3rät\n2d3räu\n4dre.\nd4rea.\nd4reas\n3d4reck\n2dreg\n3d4reh\n2d3reic\nd4reiv\n4drem\n4d3ren\n2d3rep\n4d3rer\n4dres.\nd4resc\n2d3rh\nd3ri\nd4ri.\n3d4ria\n2d5ric\nd4rid\nd4rie\nd5rieg\nd4rif\nd4rik\nd4ril\nd4rin.\n3d4risc\n3d4rit\n4dritu\nd3rob\nd3roc\n2d3rod\nd4roi\n2d3rot\nd3rou\n2d3rov\nd3rö\ndrö2s1\nd5rub\n3d4ruc\n2d3ruh\ndrunge3\n2d5rut\ndrü1b\ndrü5cke\n2d1s\nd4s1amt\nd2san\nds3assi\nd2sau2\nds1än\n4dsb\nd4schin\nd2s1e2b\nd2s1ef\nd3sei\nds2eig\nd4seins\nd2s1eng\nd2s1ent\nd2s1erf\nd2serh\nd2s1erk\nds1err\nd2s1erz\ndse4t\nd4s1eta\nd3s2ha\nd3sho\nd2s1im\nds2inf\nd3s2kan\nd3skul\n4dsl\nd2s1op\ndso2r\nds1ori\nd2sö\nd2s1par\nds1pas\nd2spä\nds2po\nd2spro\nds2pu\ndss4\ndst4\nds3tab\nd4stag\nd4s3täti\nd2ste\nd4stea\nd3stei\nd3stell\nd4stem\nd3s4tern\nds2ti\nds4til\nds4tip\nds2tu\nds1ums\nd2sun\nds2zen\n2d1t\ndta2d\nd5tea\nd2th\nd4thei\ndt3ho\ndto2\nd3tö\ndt3r\ndtran2\ndt5s2\nd3tü\n1du\ndu1alv\ndu1ar\ndub3l\ndu2bli\ndu2f\n2d1ufe\n2d1uh\ndu1i\n2d1umb\n2dumd\n2d1u2m1e\n2dumf\n2dumg\n2d3umk\n2duml\nd2ump\n2dumr\nd1ums\nd2ums.\n2d1umv\n2d1un3d\ndund2a\n2d1unf\ndung4\ndun3ke\ndun2kl\n2dunr\ndun2s\n2dunt\ndu1o\ndur2\n2d1url\n2dursa\ndu4schn\ndu4schr\ndu4schw\ndus3t\n2düb\n3düf\n3dün\n2d1v2\n2d1w\ndwa2\ndwest3\ndy2s\n2d1z\n2e1a\ne3a2b\neab3l\nea2c\nea3der\neadli4\nea2dr\nea2g4\nea3ga\nea4ge\nea3gl\neak1\neakt2\nea2la\ne3alei\ne4aler.\neam3\neam1o\nea2na\ne2ano\ne3ar.\nea2ra\ne3a4rene\ne3arr\ne3arv\ne2as\neas3s\neat4e2\neater1\ne3ath\nea5tr\neat3s2\ne3at5t4\ne3au2f\ne3aug\neau1st\ne1ä2\ne1b\n2eba\ne3b2ak\n2ebed\nebe2i\n2ebel\neb2en\nebens3e\n2ebet\n2ebl\neb3ler\neb4leu\ne3blie\neb3lo\neb2lö\n2eb2o\nebot2\nebö2s\n2ebr\neb3rei\neb4ru\neb2s1\neb6sche\nebse2\nebs3pa\neb3sta\neb4stät\nebs3tem\nebs3t2h\neb3str\ne3bu\nebu2t1\n2e3ca\ne1ce\nech1ä\n2e3che\nech1ei\ne6ch5erzi\ne1chi\nech3l\nech3m\nech3n\ne2cho.\nech1o2b\ne2ch3r\nech3ta\nech3t4ei\ne1chu\nech1uh\nech1w\ne1ci\neci6a\neck3se\n2eckt\n2e1cl\n2eco\ne3cr\nec1s4\n2ect\ne1d\ne3d2a\ned2dr\ned2e\nede2al\ne3dei\nede3n2e\nedens1\neden4se\neden4sp\nede2r\neder3t2\nedi4al\ne3d2o\ned2ö\neds2ä\ned2s1es\ned2s1o\ned2s1p\ned2s3tr\ned2su\nedu2s\ne3dy3\n4ee\nee3a2\neeb2l\nee2ce\nee1ch\nee2cho\nee2ck\neede3\need3s2\nee1e\ne1eff\neef4l\neef3s\neeg2\ne1ei\nee1im\neein4se\neel2e\nee2lek\nee3len\ne1emp\ne1en\neena2\nee4nag\ne2enä\ne2enc\nee3ni\ne2eno\neen3s\ne1e2pi\nee1ra\ne1erbt\ne1erd\nee3r2e\nee4r3en4g\neere2s\nee4ret\ne1erk\nee1rö\neer2ös\neert2\ne1ertr\nee3r2u\ne1erz\nee3s2\nees3k\nee3ta\nee4tat\nee2th\nee1u2\neewa4r\ne1e2x\ne1f\n2ef.\n2efa\ne2f1ad\nef1ana\nef1ar\ne2fat\ne2fäu\n2efe\ne3fe.\ne2f1e2b\nef1em\ne2fent\nef2er\n2eff.\n1effi\nef2fl\n2efi\ne2f1i2d\ne2f1ins\nefi2s\n1efku\n2efl\ne3f4lu\n2e3f2o\ne3fra\nef3rea\nef3rol\nef3rom\nef4rü\nefs2\nef3so\nef3sp\nef2tan\n2efu\ne2fum\n2efü\ne1g\negas3\negd4\ne3ge\nege4n3a4\nege2ra\nege4str\nege1u\ne2glo\ne2gn\neg3ni\neg4sal\neg4se4r1\neg4sto\neg2th\n2egu\negung4\negus3\n2e1ha\neh1ach\ne3h2al\neh2aus\n2e1hä\ne1he\neh2ec\neh1eff\neh2el\nehen6t3\n1e2hep\ne3her\nehe1ra\nehe3str\ne1hi\neh1int\neh1lam\neh1lä\nehle2\nehl3ein\neh4lent\neh5l2er\neh2lin\neh3lo\nehl2se\n2ehm\neh3mu\ne1ho\ne3hol\nehr1a2\nehr1ä\nehr1e2c\neh2rei\nehr3erl\nehr6erle\nehre3s\neh3ri\neh1ro2\nehr1ob\nehr1of\nehs2\neh3sh\neh1ste\n2eht\ne1hu\ne2hunt\ne1hü\neh3üb\neh1w\ne1hy\n2ei3a2\n4eib\nei2bar\nei2bl\neibu4t\nei4b3ute\nei2cho\ne2id\nei2d1a\nei3dan\nei3de\nei4d3err\n2eidn\nei3dra\nei1e\n4eien3\neienge4\n1eifr\nei3g2a\n4eigeno\neig2er\n2eigew\nei3gl\n1ei2g3n\n2eigru\n2eigt\n2eigu\neik2ar\nei3kau\neik4la\ne4il\n2eil.\nei2lar\nei2lau\n2eilb\neil3d\nei4lein\neilen1\neil3f4\neil3ins\n2eiln\n1eilzu\nei2m1a4g\neim3all\nei2mor\ne1imp\neim2pl\nei2n1a\nei4nas\nei4nä\nein3dr\n2eindu\nei4neng\nei2neu\n2einfo\nein4fo.\nein4fos\nein3g2\nein4hab\ne1init\nein3k\nein6karn\n3einkom\nei2n1o2\n3einsat\nein6stal\nein4sz\ne4inver\nei3o2\nei1p\neip2f\n2eir\nei3re\ne1irr\ne2is.\nei2sa4\nei6schwu\nei4s3erw\neis2pe\neis4th\nei1sto\nei2sum\ne2it\nei2tab\nei2tan\nei2tar\n2eitä\nei3te\nei2th\nei2tro\neitt4\neit3um\n2eiu\n2e1j\ne1k\nek2a\n1ekd\ne3ke.\ne3ken\ne3kes\ne3key\ne3k2l\nek4n\nek2o\nek4r\nek1s4t\n2ekt\nekt4ant\nekt3erf\nekt3erg\nek4t3erz\nekt2o\nek5tri\nek2u\ne3k2w\ne1la\nela4ben\nel2abt\nela2c\nel1af\nela2h\ne2l1ak\ne2l3a2m\nel4ami\nel4amp\nel1ans\nel1anz\n2elao\ne2l1ap\ne2l1a2r\nel3ari\nela4s\nel1asi\nel1asp\nel2ast\n2e1lä\n3elbis\nel2da\neld5erst\nel4d3erw\neld3s2\n2ele.\nelea2\nele2c\n2eleh\n2elei\ne6l5eier.\ne2l1ein\ne3leine\ne4leing\n1elek\ne2l1el\n1e2lem\ne3lem.\nel1emp\n2e3len.\ne4lense\ne2l1ent\ne3lep\nel1erd\nel1erf\ne4ler4fa\ne2l1erg\nel1erk\nel1erl\ne4ler4la\ne4l3ernä\ne2l1err\n2eles2\nel1ess\ne4l1e4ta\ne3leu\n2elev\nele2x\n1elf.\nel3fe\nelf4l\n1elfm\n1elft\nelgi5er.\nelgi5ers\n2eli\ne2l1id\ne3lie\neli2ne\nel1ita\nel3kl\nel3lan\nel3le\nel5le.\nell3ebe\nel4l3ein\nell3eis\nel3lin\nell3sp\nelm2a\n2eln\nel5na\n2elo\ne2lof\ne2lol\nelon2\nel1ope\ne2l1or\nelo2ri\nel2öf\nelö2s\nel2sum\nelte2k\nelt3eng\n3eltern\nelto2\nel2t3r\nelt3s2k\nelt3s2p\n2e1lu\ne2l1um\nel1ur\nel3use\ne1lü\ne2lya\n2elz\nelz2e\nel2zwa\ne1m\n2ema\ne2m1ad\nema2k\ne2m3anf\ne2m1ans\n3emanz\nem2d3a2\ne3m2en\nemen4t3h\ne6mentsp\ne2m1erw\neme2s\n1e2meti\ne2m1im\nem1int\nemi3ti\n2emm\nemma3u\nem2mei\ne2mop\n3empf\nem3pfl\nem2p3le\nem2sa\nem2spr\nem2st\nem3t2\n1emul\n2emü\ne2n1a\n4ena.\n2enac\ne3nad\ne4naf\n4enah\ne4nak\nena3l2i\n4enam\nen4ame\ne4nand\nen3ang\nen3are\nen2asc\n4enat\nen3att\ne3naue\ne2n1är\nen1äu\nen4ce.\nen3d2ac\nen2dal\nen4d3ess\nend4ort\nend3rom\nend3si\nend3s2p\nend3sz\nend2um\n2ene.\nene4ben\nen1e2c\ne2neff\ne4nein\ne2n1el\nene4le\n2enem\n2enen\ne4n1ent\nen4entr\n4e3ner.\ne2n1erd\ne2nerf\n1e2nerg\ne4nerh\ne4nerk\ne2n1erl\ne4n3ermo\n4enern\ne2n1err\ne2n1ers\ne2n1ert\ne2n3eru\ne2n1erw\ne4nerz\n2enes\ne4n3ess\nen3f\nenf2a\nenf2u\n1engad\n3engag\nen3ge\nen3g2i\nen2gl\nen3glo\n1engp\neng3se\ne3ni.\ne3nic\ne4nid\ne3nie\neni3er.\neni5ers.\ne2n1i4m\ne2n1in\ne3nio\n2enis\neni3se\ne3nit\n2eniv\nen3k2ü\ne2n1o2b\nenob4le\ne2nof\nen1oh\ne3nol\neno2ma\nen1on\ne2n1op\ne2n1o2r\nenost3\ne3not\neno2w\n2e1nö\nen1ö2d\ne4nr\nen3sac\nen2sau\nen5sch4e\nen2seb\nens2el\n1ensem\nensen1\nen3ska\nen3s2po\nenst5alt\nen4s3tät\n2ensto\ne4nt\nent4ag\n1entd\nen2teb\nen4terb\n1entfa\n3entga\nen2thi\n3entla\n1entn\nen4t3rol\n3entspr\n2entü\n1entw\n4entwet\n1entz\nen1u\n2enut\ne1nü\nenü1st\n4enwü\ne1ny\nen4z3erf\nen4z3erg\nen4z3erk\nenz3ert\ne1ñ\n2eo\ne1o2b1\ne1of\neo2fe\ne1oh\ne4ol\ne1on.\ne1ond\ne1onf\ne1onl\ne1onr\ne1ons\ne1ope\ne1opf\neop4t\ne1or\ne3or.\ne3orb\ne3ors\ne3orw\neo1s2\ne3os.\neo3ul\ne1ov\ne1ö2\ne1p\ne3pa\nepa2g\ne3p2f4\n1episo\nep3le\n1e2poc\nep2pa\nep4pl\nep2pr\nept2a\nep2tal\ne3pu\nepu2s\ne1q\ner1a\ne3ra.\ne3rad.\ner3adm\neraf4a\nera1fr\nera2g\ne1rai\ner3aic\ne2rak\ne1ral\ner3all\neran3d\ne3rane\ner3anf\ne2ranh\ner3anm\ne1rap\ner3apf\ne2rar\ne3rari\ne1ras\ne2r3a4si\nera2ß\ne2rath\ne3rati\ne2ratm\ne1raub\ner3aue\nerau2f\ner3aug\ne1raw\ne1raz\ne1rä\ner1äh\ner1äm\nerb2e\ner3br\nerb4sp\ner1c\ner3chl\ner3da\n1erdb\ner3de\n2erdec\nerd3erw\n4ere.\ner1eb\ne3rech\ner3echs\ner1e2ck\nere4dit\ner1eff\ne2r1e2h\n4e3rei.\ner1eig\ne2rein\ne4r3eis.\nere2l\ner1ele\n2e3rem\n2eren\n4e3ren.\ne3rena\ne4rense\ne4r3entf\ne4rentn\ne3renz\neren8z7end\n2erer\n4erer.\ne2r3erf\ne2r1erh\ne4rerl\n4erern\ne3rero\ner1err\ner1ers\ne2rert\ner1erw\n2eres\ner1ess\ner3e4ti\ner1eul\nere4vid\nerf2e\nerf4r\n4erfür\n3ergebn\n4ergehä\nerg3el4s3\n1ergol\nerg3s\nergs4t\ner3h\n1erhab\n2erhü\n2eri\ne2riat\ne3rib\n4e3ric\n4e3rie\neri3e4n3\ne3ri3k4\n4e3rin.\ner1inb\ne2r1ini\ner1ink\ner1int\ne3rio\ner1ita\n2erk.\n1erklä\n2erkre\nerk3t\n3erlebn\nermen4s\nerm3ers\nern1os\ne1ro\ne3ro.\ner3oa\ner1o2b\ner1of\ner1oh\ne3ron\ne2r1o2p\ne4ro2r\ne3ros\ne3row\ner1ö\nerö2d\n2erök\ner3p4\ner3rä\n2errü\ners2a\ner3se\ners2i\ner3sk\ner3smo\ner3sn\ner3sp\ner3sz\nert2ak\ner6terei\ner4ters\ner2tho\n4erti\nert3ins\nert4ra\nerts2e\n2eru\neruf4s\ner1u4m\ner1und\nerung4\ner1uns\ner3uz\nerü4b\n3erweck\n6erweis\nes3ab\nes2ach\nes3ak\nes3anz\ne3s2as\ne4s3ato\n2esb\nes2c\nes3cap\ne3sce\nesch2\ne3scha\ne2s3ein\nes2el\nese4ler\nes3eva\n2esf\n4esh\nes2har\nes2hu\nes2id\ne2sil\nes3int\nes2ir\nes2kat\ne4ske\nes3kl\nes3ku\ne4sky\nes3l\nes4log\n2esm\nes2ort\ne3sot\nes2ö\n2esp\ne3s2pek\ne3spi\ne3s2por\ne3s4pra\n2esr\nes2sau\nes3sc\nes3se\n4essem\ness4e3re\ness3erg\n2esso\nes2sof\nes2s1pa\nes2spu\nes3str\nes3stu\nestab4b\nest1ak\ne1star\ne4starb\n1e2stas\ne1stat\ne1s2tec\ne3stel\nes4t3eng\nes4t3erh\nes4t3ess\ne1stil\ne2stip\nestmo6de\nest3ori\ne1str\nes4tri\nes3trop\ne1stu\nes4tü\ne2s1um\nes3ums\nes3w\ne3sy\nes3z\ne1ß\neße3r2e\ne1t\netab4\net1am\n3etap\net4at\net1äh\ne3te\ne4tein\net2en\neten3d2\nete2o\neter4hö\neter4tr\net2h\net3hal\net3hü\ne3ti\neti2m\neti2ta\n2e3to\neto2b\ne4t1of\netons4\ne3tö\n2etr\ne4traum\ne6t3rec\ne2tres\net4rig\netsch3w\nets2p\net3su\nett1a\net2tab\net2t3au\net2tei\nette4n1\net2th\net2t3r\net4tro\nett3sz\net4t1um\ne3tü\netwa4r\n2etz\net2zä\net4z3ent\netze4s\net2zw\neu1a2\neu3erei\neue6reif\neu2esc\neu2ga\neu4gent\neu3g2er\neu4gla\neugs4\neuil4\neu1in\n1euk\neu2kä\ne1um\ne3um.\ne3umb\ne3uml\ne3um2s\neum4sc\neums1p\neum3st\n2eun\neun2e\neu4nei\ne3un2g\neu2nio\neun3ka\neu1o2\neu1p\neur2e\n3eu3ro\neu3sp\neust4\neu1sta\neu1sto\neu1str\n2eut\neut2h\neut6schn\n2eux\neu2zw\ne3ü\n2e1v\ne2vela\ne2vent\n4ever\neve5r2i\ne3vo\ne1w\n2ewa\ne3wä\newä2s\n2ewe\ne2we.\newinde3\ne3wir\newi2s\ne3wit\new2s\n2ex.\nex3at\n1e2xem\nex1er\ne1xi\ne2x1in\n1exis\nex3l\n3exp\n2ext.\nex2tin\nex2tu\n2exu\n2e3xy\ney1\ney4n\neys4\ne1z\ne3z2a\ne2z1enn\ne3zi\nezi2s\nez2w\né1b\né1c\né1g\négi2\né1h\né1l\nélu2\né1o\né1p\né1r\né1s\né1t2\né1u2\né1v\né1z2\nè1c\nè1m\nè1n\nè1r\nê1p\nê4t\n1fa\nfab4\nf1abe\nfa2ben\nfab5s\n3fac\nfa4cheb\nfacher5f\nfa2ch1i\nfa2cho\nf1ader\nfa2dr\nf4ah\nfaib4\nfa2ke\nf2al\nfa3l2a\nfal2kl\nfal6l5erk\nfal6scha\nfal6schm\nfal3te\nfalt2s\n2fanb\n2fanf\nfan2gr\n2f1ank\n2fanl\nf1anp\n2fanr\nfan3s\n2fanw\nf1an3z\n2f1ap\nf2ar\nfar2br\n2f3arc\n3fari\nfarr3s\n3f4art\n2f3arz\nfa3s4a\nfa3sh\nf3at\nfa2to3\n2f1auf\nf3aug\nf1ausb\n3f4av\nfa2xa\n1fä\nfä1c\nfäh2r1u\n2f1ärm\nfä2ßer\nf1äu\n2f1b2\n2f1c\n2f3d4\nfdie2\n1fe\nfeatu4\nfe2c\nf2ech\n2f1eck\nfe2dr\nfe2ei\nfe1em\nfef4l\nfeh4lei\nf4eie\n2f1eing\n4f1einh\nfe1ini\n2f1einw\nf1eis\nfek2ta\nfe2l1a\nfel2dr\n2fe2lek\nfe2l1er\nfe2les\nfe2l1o\nfel4soh\nfel3t\nf2em.\nfem4m\n2femp\nfe2nä\nfen3g\nfe2no\nfen3sa\nf1ent\nf2er.\nfe1ra\nfer2an\nfe4rang\nfe4r3anz\nfe2rau\nferde3\nf2ere\nfer2er\nfer3erz\nf1erfa\nf2erl.\n4ferneu\nf4erpa\nf2ers.\nf2ert\nf1erw\nfe2st\nfest1a\nfest3ei\n2f1e4ta\n3fete\nfet2t3a\nfeuer3e\nfeu4ru\n3few\nf1ex\n2fexp\n3fez\n1fé\n2f1f\nff3ar\nff1au\nff2e\nffe2e\nf2f3ef\nff3ei\nffe1in\nffe2m\nf2f3emi\nff4en\nf2fex\nfff4\nff3l\nff4la\nff4lä\nff4lo\nf3flu\nf3flü\nf3f4rä\nff3ro\nff3rö\nff2s\nff3sho\nffs3t\nffs4tr\n4f3g2\nfge3s\n2f1h2\n1fi\n3fi.\nfi3at\nfid2\nfi4ds\nfid3sc\nfien3\nfi1er2f\nfi2kin\nfi3kl\nfik1o2\nfi2kob\nfi2kr\nfi2l1an\nfil4auf\nfil3d\nfi2les\nfilg4\nfi3li\nfi4lin\nfil2ip\nf2ina\nfi3ni\nfin2s\nfin3sp\n2f1int\nfi2o\nfi3ol\nfi2r\nfi3ra\n3fis\nfis2a\nfisch3o\nfis2p\nfi2s5t\nfit1o2\nfi2tor\nfi3tu\n3fiz\n2f1j\n4f1k4\nf2l2\n2fl.\nf3lad\nf3lap\n1flä\n3f4läc\n2f5läd\nf3län\n2f3läu\n2f3leb\nf4lee\n2f3lein\nf3ler\nf4lé\nf3li.\n3f6lim\nfli4ne\n2f5lon\n1f4lop\n1f4lot\nflo2w\nf3lö\nf4luc\n1f4lug\nflu4ger\nf4lü\n2f3m2\n2f3n2\nfni2s\n1fo\nfob2l\n2f1of\nfoli3\nfo2na\nfo2nu\n2f1op\nfo1ra\n4f3org\nfo3rin\n3form\nfor4m3a4g\nforni7er.\nfor4st\nfor4tei\nfor2th\nfor2t3r\nfor3tu\n2f1o2x\n1fö\n2föf\n2f1ök\n2f1öl\nför2s\n4f1p2\n2f1q\nf2r2\nf4rac\nfrach6tr\nf5rad\nfra4m\nf3rand\nf5rap\n1f4rän\n2fre.\nf3rec\nf3red\n2freg\nfreik2\nfrein4\nf3rep\nf4reu\n2f3ric\nfri3d\nfri2e\n2frig\n1fris\nf4risc\nf3roc\n1f4ron\nfro2na\nfro2s\nf3rot\nf3ru\nf3rü\n4f1s\nfs1all\nfs4amm\nf2san\nfs3ar\nf2s1as\nf2sauf\nf2saus\nf2saut\nf3sc\nf4sce\nf4schan\nf4schef\nfs4co\nfs1e2b\nf4s1ehr\nf2s1em\nf2s1ent\nf2s1er\nfse4t\nf4s1eta\nf3si\nf2si2d\nf3s2kie\nf2s1o2\nf3span\nf2s1pas\nfs1pen\nf2sph\nf3s2pl\nf3s2por\nfs1pr\nf2spre\nfs2pri\nf2spro\nfs2pru\nfs3s4\nfs2t\nf2stas\nf4s3täti\nf4stech\nf3stei\nf3s4tel\nf3stern\nfs3th\nf2stip\nf3st4r\nf4s3tres\nf4s3tüte\nf2s1un\nf2sü\nf3sy\n4f1t\nf4ta.\nf2tab\nft1a2be\nft1af\nf2t1al\nft1an\nft1ar\nf3tat\nft1e2h\nft1eig\nft1eis\nf4t1ent\nf4t1e4ti\nf2th\nf4thei\nft3ho\nft1op\nf3tö\nf2t3ro\nf2t3rö\nf3t4ru\nft2s1\nftsa4\nft4sam\nft3s2c\nft4sche\nftse4\nft4seh\nfts3el\nft3st\nft4s3tan\nft4s3tä\nfts2ti\nft4stri\nf2tum\nft1url\nf3tü\nftwa4\nft3z2\n1fu\n3fug\n3f2uh\nf1um\n2f1unf\nfung4\n2f1u2ni\nfun2kl\nfun2ko\nfun2k3r\n2f1unm\n2funt\nf2ur\nfu4re.\nfus2sa\nfus2s1p\nfus2st\nfu2ß1er\n3fut\n1fü\n2füb\nfü2r\n2f1v\n2f1w\n1fy\n2f1z\nfz2a\nfzeiten6\nfzei8tend\nfz2ö\nfzu3\nfzu4ga\n3ga.\n2gabf\nga2b5l\ngab4r\n2gabz\nga1c\n2gadl\n2ga2dr\nga1fl\nga3ge\n5gai\nga1k\nga2ka\ngal2a\ng4amo\n2g1amt\n2ganb\ngan3d\ngan2g1a\n4gangeb\ngan2gr\n2ganh\n2g3anku\n2ganl\ng3anla\n3g2ano\n2ganw\nga1ny\n2garb\n2garc\n3gard\n2g1arm\nga3r2o\ng1arti\nga3ru\n2g1arz\nga2sa\ngas3ei\nga2si\nga2sor\nga3sp\nga4spe\nga4spr\ngas3s\ngas4ta\ngas5tan\nga4ste\ngas4t3el\ngat2a\n2gatm\ngat4r\ngau1c\n2g1auf\ng2auk\ng1aus\n2g1aut\n2g1äp\n2gärz\ngäs5\ngä4u\n2g1b2\ngber2\ngbi2\ngby4t\n2g1c\n2gd\ng1da\ng2d1au\ng2d1er\ngd1in\ng1do\ng1dö\ng1d3r\ngd3s2\ngdt4\ngd1u\n1ge\nge3a2\ngeb2a\ngebe4am\ngeb4r\nge1c\nged4\nge1e2\nge3ec\nge2es\ngef4\nge3g2l\nge1im\nge2in.\ngein2s\nge2int\ngein2v\nge1ir\nge2is\n2g1eise2\ngei3sh\ng2el\nge4lanz\ngelb1r\ngel4b3ra\ngel6ders\nge3le\nge5leh\nge4l3ers\nge4less\ngell2a\nge3lor\ngels2t\ngel3ste\ngel3sz\ngel3t2a\nge3lum\nge3lü\ngelz2\nge3mi\ngem2u\n3gen\nge3na\nge4nam\nge4nar\ngen4aug\ngen2d1r\ngen1eb\nge3nec\ngen3eid\ngen3ern\ngen3g\ngen3n\ngen4sam\ngen3sz\n2g1entf\ngen3th\n4gentw\ngeo2r\nge1ou\nge3p4\nge1ra\nge2rab\n4g3ereig\nge4reng\nge4ren4s\nge4r3ent\nger2er\ngerin4f\nger4inn\ngerin4t\ngerm4\nger3no\nge1r2ö\nger4sto\nge3r2u\ng2e1s2\nges3auf\nge3sc\nges3elt\nge2s3er\nge3si\nges4pi\nges3s2t\ngest2\nge3ste\nge4s3ter\nges3th\nge3t2a\n2getap\nge5tr\nge3t4u\nge1ul\nge1ur\n2g1ex\n2g1f4\n4g1g\ngga4t\ng3ge\ngge2ne\ng2g3l\ngg4lo\ng2g3n\ngg4r\n2g1h\n4gh.\ngh2e\n3g2het\n3g2hie\ngh1l\n3gh2r\ng2hu\ngh1w\ngi3alo\ngie3g\ngi2e1i\ngi2el\ngien2e1\ngie1st\ngi2gu\ngi2me.\ngi4mes\ngi2met\n2g1ind\ngi3ne\ngin2ga\n2g1ins\n2g3isel\ngi3t2a\ngi3tu\ngi4us\n2g1j\n4g3k2\n4gl.\ng1lab\ng1lac\n3glad\ng2lade\n2g1lag\n3glanz\n3g2laub\n2g1lauf\n3glät\n2gläuf\ng2l4e\n2gle.\n3glea\n2g3leb\ng3lec\ng3leg\n2gleh\n4g3lein\nglei4t5r\ng3len\n4g5ler\n2gles\ng3lese\ng4lia\n2glib\n3g2lid\n3g2lie\n2glif\ng2lik\n4glin\ng2lio\n2glis\n4g3lisc\n3g2lit\ng2liz\n3g2loa\n3g2lob\ng3loch\nglo3g\n3g4lok\ng2lom\n3g2lop\n3g2lot\n2gls\n2g1lu\nglu2t\n3glü\ng2ly\n2g1m2\ng1n\n2gn.\ng2n2a\ng4na.\n2gnac\ng4nat\n3g2nä\ngn2e\ng3neh\ngne2tr\n2gneu\n2gng\ng2nie\ng2nif\ng4nin\n2gni2s1\n3g2no\ngno1r\n4g3not\n2gnp\n2gns\n2gnt\n2gnu\n3g2num.\ng2nü\ng2ny\n2gnz\ngo4a\ngoa3li\n2g1of\n2gog\n2g1oh\ngo1i\ngol2a\n2gonis\n2g1ope\n2g1opf\ng2o1ra\n2gord\n2gorg\ngo2s1\ngo3st\ngo3th\ngot6t5erg\ngo1y\n2g1p2\n2g1q\ng2r4\ngra2bi\ngra2bl\n2gradl\n2g3rah\n2g3rak\ngrammen6\ngram8m7end\n2g3räu\n2g5re.\ng4reb\n2g3rec\n2g3rede\ng4re2e\n2g3reic\n2g3rein\ng3reit\ng4rem\n2g3renn\ngren6z5ei\ng4rer\ng3ret\ng3rev\n2g3ric\ngri2e\ng3riese\n3grif\n2grig\n2g3ring\n2groc\n2groh\ngron4\ng4ros\ngros6sel\ngro4u\n2g3röh\ng4ruf\n2g3rui\n2g3rum\n3g4runs\n3g4rup\n2grut\n2g3rüc\n3g4rün\n4g2s1\ngsa4g\ng3s2ah\ng4s3a2k\ng3sal\ng4salt\ngs3ama\ngs3an\ngs3ar\ngs3aug\ng3s2c\ng4sca\ng4s3ce\ngsch4\ng4schef\ngs4chi\ng4sco\ng4s3cr\ngse2\ngs2eh\ng3s2eil\ng3sel.\ngs3eli\ng3seln\ngsen1\ngs3er\ngs5erk\ngse4t\ng4seta\ngsi2d\ng3sil\ng4sl\ngso2\ngsp4\ng3s2pek\ng3spi\ngs4pie\ng4spin\ngs2pit\ngs3pl\ng3s2por\ngsrat4\ngsrü2\ngs5s4\ngs3ta\ng3stan\ng3star\ng3s4tati\ng4s3tä\ng5stäm\ng3stel\ngst3ent\ngst3err\ng1steu\ngst2he\ng3stir\ng3sto\ngs3toc\ng4stol\ngs3top\ng4s3tor\ng3stö\ngs3tr\ngst4ra\ngs4trat\ngst4ri\ngs4t3ros\ng3stu\ng4stur\ngs3tü\ngs4tüc\ng4sw\ng3sy\n2g1t\ng3te\ngti2m\ngt4r\ngt2s\ng3tü\n1gu\ngu3am\ngu1an.\ngu1ant\ngu1c\ngu2e\n2gued\nguet4\n2g1u2f\n2g1uh\ngu1ins\ngu1is\n3gumm\n2g1unf\ng2ung.\ngunge2\n4gungew\n2g1ungl\ng2un4s\n2gunt2\n2g1url\ngurt3s\ngu2s3a\nguschi5\ngus4ser\ngus2sp\ngus2st\ngu4st\ngu2t\ngut1a\ngu4t3erh\ngut3h\n2güb\ngür1\ngüs3\n2g1v\n2g1w\n2g3z2\n3haa\nhab2a\nhab2e\nh2abs\nha2cho\nha2del\nha4din\nh1adle\nhaf3f4l\nhaft4s3p\nh1ah\nha1kl\n2h2al.\nhalan4c\nha2lau\nhal2ba\nhal4bei\nhalb3r\n2hale\nhal2la\nhal6lerf\nh1alp\nhal2st\nhal4t3r\nh1amt\nh2an.\nh2and\nhand3s\nh4ann\n2hanr\n2hant\nh1ap\nha2pl\nha2pr\nh4a3ra\n2harb\nh2ard\nh1arm.\nhar4me.\nhar4mes\nhar2th\nh1arti\nh2as\n2ha3sa\nhasi1\nhat5t2\nhau3f4li\n2h1aufm\nh1aukt\nhau2sa\nhau2sc\nhau4spa\nhau5stei\nhau6terk\n2hauto\nhau2tr\nh1äff\nhä6s5chen\nhäu2s1c\nhä3usp\n2h3b2\nhba2r3a\n2h1c\n2h3d4\nhdan2\n2hea\nhe2ad\nhe3be\nhe4b1ei\nhe2bl\nhe3br\nhe5ch2e\nhe1cho\nh1echt\nhe3cke\nhed2g\nhe3di\nhe2e3l\nhee4s\nhe2fan\nhe2fä\nhe2f1ei\nhef3erm\n2heff\nhe4f3ing\nhe2f3l\nhe2fr\nhe3fri\nhe2fu\nhe3gu\nh1eie\nh1eif\nh1eig\nhe2im\nheim3p\nhei4mu\nheine2\nhei4neh\nh1eink\n4heio\nhe1ism\nhe1ist\nheit4s3\nh1eiw\nhe2l3au\nhel1ec\nh3e2lek\nhe3len\nhel3ers\nhe3li\nhel4l3au\nhel4mei\nhe3lo\nhe4lof\nhe2lö\n3hemd\nhe3mi\n3hemm\n4h1emp\nh2en.\nhe4n3a4\nhe2nä\nhend2s\nhe2n1e2b\nhen3end\nhen3erg\nhe2net\nheng2\n2heni\nhe2no\nhenst2\nhen5tr\nh1ents\n2h3entw\nhen3z\n4he2o\nhe3on\nhe3op\nhe3ph\nher3a2b\nhe2ral\n2herap\nhe3ras\nherb4s\nhe4reck\n4hereig\nhe4r3eis\nhe2rel\nhe4rerw\nh1er2fo\nh1erfü\nherg2\nherin4f\nhe6rin6nu\nherin4s\nherin8ter\nh1erke\nh3erlau\n2herm\nhe3ro\nhe4r3o4b\nh1erö\nhert2\nher3th\nher2zw\nhe1sta\nhe2s5tr\nhe2tap\nheter2\nhe3th\nhet2i\nhe3t4s\nh2e2u\nheu3g\nhe3x\nhe1x4a\nhe1y2\n1hè\n2h3f4\nhfell1\nhfel6ler\nhfi2s\n2h3g2\nhget4\n2h1h2\n2hi.\n2hia\nhi2ac\nhi2ang\nhi1ce\nhich6ter\n2hi3d\nh2ide\nh1i4di\nhi2e\nhi3ens\nhier1i\nhie4rin\nhiers2\nhif3f4r\nhi2kr\nhi2l3a4\nhil2fr\nhi2n\nh1indu\nhi3nel\nhin2en\nh1inf\nh1inh\nhi3n2i\nhin3n2\nhi3no\nhin3s2\nhin4t1a\n2hio\nhi4on\nhi3or\n2hip1\nhi2ph\nhi2pi\nh2i2r\nhi3ra\n2hi3re\nhi3ri\nhirn1\nhir4ner\nhi3ro\nhir2s\nhis2a\nhi2se\nhis2p\nhi2st\nhi1th\nhi3ti\n2hiu\nh1j\n2h1k4\n2hl\nh4lac\nhla2n\nhl1anz\nh1las\nh1lat\nh1laut\nh3läd\nh1läs\nh1läu\nhlb4\nhld4\nh3leb\nhle3e\nh5len.\nhlen3g\nhl2enn\nh3ler\nhle2ra\nhl1erg\nh6l3ernä\nhle3run\nhl1erw\nh4lerz\nh3les\nh4lesi\nh3lex\nhlg4\nh2lie\nh2lif\nhl1ind\nh2lip\nh2lis\nh3list\nh2lit\nhll2\nhlm2\nh2lo\nh3loc\nhl1of\nhl1op\nh4lor\nhlo2re\nh3losi\nhl2ö\nh3löc\nh2lös\nhl2san\nhl2ser\nhl3sku\nhl3slo\nhl3t2\nh3luf\nh3luk\nh1lüf\n2h1m\nh2mab\nh3mag\nh3man\nh3mar\nh4mäc\nh4mäh\nh4mäl\nh4mäu\nh3me.\nhme1e\nhme1in\nh3men\nhmen2s\nhme2ra\nh2mo\nh4mon\nh3mö\nhm3p4\nhm2s\nhm3sa\nhms1p\nh2mu\n2hn\nh2na\nhn1ad\nh3nam\nhn1an\nh2nä\nhn3d4\nhn2e\nhn3eig\nhn3ein\nh2nel\nhne4n1\nhne4pf\nhner3ei\nh3nerl\nh3nerz\nhn3ex\nh2nic\nh2nid\nh2nie\nhn1im\nhn1in\nh2nip\nhn3k4\nh2nor\nhn3s2k\nhnts2\nh1nu\nh2nuc\nh2nul\nhn1unf\nh3nunge\nho2bl\nho2ch3\nho2cka\nho6ckerl\nhock3t\n2hod\nhoe4\nho2ef\nho4fa\nho2f3r\n2hoi\nhol1au\n4holdy\n3hole\nho2l1ei\nhol3g4\n4holo\nho4lor\n3hol3s\nh1o2ly\n3holz\nhol6zene\nhom2e\nho2mec\nho2med\nh2on\nhono3\n2hoo\n2hop\nho1ra\nhor3d\nh1org\nho4sei\nho3sl\nho2sp\nho4st\n2hot.\nho3th\nhotli4\n2hot3s2\n3hov\n2ho2w1\nh1o2x\nho1y2\n1h2ö\nhö2c\nhö3ck\nh4ör\nhö2s1\nh3öst\n2h3p2\nh1q\n2hr\nhr1ac\nhr3ad\nh1rai\nh1rane\nh3räu\nhr1c\nhr3d\nh2rec\nh3rech\nh3red\nh3ref\nh4rei.\nhrei4ba\nh3reic\nh4r3eig\nh3rel\nh3r2en\nh3rep\nhr2erg\nhr2erk\nh6rerleb\nhr2erm\nhr2erz\nh3re2s1\nhre2t\nh2r1eta\nh3rev\nhrf2\nhrg2\nh2ri\nh3ric\nh4rick\nhri4e\nh3riesl\nh3rin\nh4rine\nh4rinh\nh4rist\nh2rob\nh3roh\nh3rol\nh4rome\nh4romi\nh4ron\nh2ror\nh3rou\nhrr4\nhr2s1ac\nhr2s3an\nhr2sau\nhr3schl\nhr2s1en\nhr2ser\nhr4set\nhr4s1in\nhrs3k\nhr4s1of\nhr2su\nhr4sw\nhr2tab\nhr2tan\nhr2th\nhr2tor\nhrt3ri\nhr2tro\nhrt2sa\nhrt2se\nh3ruh\nhr1ums\nh3rü\nh4rüb\nh2ry\nhrz2\n4hs\nh2s1ach\nh2san\nh2sau\nh4schan\nh2s1ec\nhse4ler\nh2s1erl\nh3s2ex\nh2s1ing\nh2s1of\nh2s1par\nh2sph\nhs2por\nh2sprä\nh2spro\nhss2\nh1sta\nhst3alt\nhst2an\nh2s3tau\nh1stec\nh3stein\nh5stell\nh3s4terb\nhst2he\nh1s2ti\nh1sto\nh2stor\nh1s4tr\nhst3ran\nhst3ri\nh1stun\nh2s1u\nhs2ung\n4h1t\nh2t1a\nh3t4akt.\nh3takts\nh3t2al\nh4t3alt\nh4t3a2m\nhta4n\nht3ane\nh3tank\nht2as\nh4t3ass\nh4tasy\nht3a2t\nh2tär\nht1e2c\nh2t1ef\nht1eh\nhte2he\nh2teif\nh4teilz\nh2t1eim\nh2t1eis\nh4t3elit\nh2temp\nh4tentf\nh4t3ents\nht3erfo\nht3erfü\nh2t1erh\nht5erken\nh4terkl\nh6terneu\nh4t3erre\nht3ersc\nh6t5erspa\nht3erst\nh6tersta\nht6erste\nh2t1erz\nhte2s\nh4t1ese\nh4t1ess\nhte3sta\nh2t1eu\nh2t1ex\nh2th\nh4thei\nhthe3u\nh4tho\nh2t1in\nhto2\nh2toly\nh2torg\nh3töp\nh4t3rak\nht3rand\nh2t3rat\nht6raume\nh4tref\nht4ri\nh4t5rin\nh2t3rol\nh2t3ros\nht3rö\nh4t1rös\nh2t3ru\nh2t3rü\nh4ts\nht2so\nht2sp\nht3spri\nht4stab\nhts2ti\nhts4tie\nht4s3tur\nht4s3tür\nhtt4\nhtti2\nh2t1urs\nh3tü\nht3z2\nhu2b1a\nhu2b3ei\nhu2b1en\nhu2b3l\nhu4b3r\nhu2bu\nhu1c\nhu2h1a\nhu2h1i\nhuko3\nhuk3t4\nhu2l3a\nhu2lä\nhu2l3ei\nhu4l3eng\nhu4lent\nhu2ler\nhu2let\nhu2l1in\nhu2lo\nhu3m2a\nh1ums\nhu2n\nh1una\nhung4s\nhu3ni1\nh1up.\nh1ups\n2hur\nhurg2\nhu3sa\nhu2so\nhus4sa\nhus2sp\nhu2tab\nhu3t2h\nhu2ti\nhut2t\nhut4zen\nhut4z3er\nhutz1i\nh2ü\nh4übs\nh3übu\nhühne4\nhüs3\n2h1v\nhvi2\nhvil4\n2hw\nh2wall\nhwe1c\nh1weib\n3hyg\n3hyp\nhy2pe.\n2hy2t\nh1z\nhz2o\nhzug4\ni1a\n2ia.\ni4aa\ni2ab\niab4l\n2iac\ni2af\niaf4l\ni4a3g2\ni2ah\ni2aj\ni2ak\ni3ak.\ni3akt\n2ial\ni5al.\nia2l1a4\nia2lä\nial3b\nial3d\ni3alei\ni3alent\ni3alerf\ni3alerh\nia4l3erm\ni3a2let\ni3a4lia\nialk2\ni3all\nial3la\nia2lor\nial3t4\nia2lu\nial3z2\ni2am\ni4amo\n2ian\nia2nal\ni3and2\nian2e\ni3ann\ni2ano\ni3ant\ni3anz\ni2ap\nia3p2f\nia1q\ni3ar.\nia2ra\n2ias\ni2asc\nia3sh\ni2asi\ni2a3sp\nias3s\niast4\ni3at.\ni3a4ta\ni4ate\ni3at4h\n1iatr\ni3ats\ni3au\nia3un\n2iav\n2iä\ni1äm\niär2\ni1är.\ni1ärs\ni1ät.\ni1äta\ni1ät3s4\n2i1b\ni2b1auf\nib2bli\nib1ei\ni2beig\ni2beis\nibela2\nibe4n\niben3a\nibi2k\ni3bla\ni3ble\ni2blis\nib2o\ni2b1ö\ni4brä\nib3ren\nib4ste\ni2bunk\ni2bunt\nibu2s1\n2ic\nic1c\nice1\nich1a\nich1ä\ni1che\nich1ei\ni1chi\ni2chin\nich3l\ni3chlo\nich3m\ni1cho\ni2ch3r\nich2t3r\ni1chu\nich1w\ni1ci\ni3ck2e\ni1cl\ni1d\nid2ab4\ni3dam\nid2an\ni2d1au\n1i2dee\ni2dei\nidel2ä\nide3so\nide3sp\n1i2dio\nidni3\ni2dol\n1idol.\n2i2dr\ni3d2sc\nid2s1p\nidt4\ni2dy\nie3a4\nie2bä\nie2bl\nie2bre\nieb4sto\nieb4str\nie1c\nie2cho\nie2ck\nie2dr\nie1e2\nie2f1ak\nie2f1an\nie2fau\nief3f4\nie2f3l\nie2fro\nie4g5l\nie3g4n\nie2g3r\nie3g4ra\niegs3c\ni1ei\ni2e2l1a\nie3las\niel3au\niel3d\niel1ec\nieler8geb\ni1ell\nielo4b\niel3sz\niel3ta\n2i1en\ni3en.\ni3ena\niena2b\nie4n3a4g\ni3e2nä\ni3end\ni2ene\nien1eb\nie3ner\nien4erf\nie4n3erg\ni3enf\ni3en3g\nienge4f\ni3enh\ni3enj\ni3enk\ni3enm\ni3enn\ni3e2no\ni3enö\ni3enp\ni3enr\nien2s\nien3sc\nien3s2e\nien3si\niens2k\nien3sp\nienst5rä\nien3sz\nie1nu\ni3env\ni3enw\ni3enz\nie1o2\nier3a2\nie2rap\ni2ere\nie3r2er\nie4rerf\nie4r3erz\nie3res\ni3ereu\ni4eri\nierin3\nier3k2\ni1ern\ni3ern.\ni2er5ni\nie2rö\nier4seh\niers2t\nier3sta\nier3ste\nier3te\niesen3s4\nies2sp\nies2s3t\nie1sta\nie3su\nie2t1a\nie4t3erh\nie4t3ert\nie2t3ho\nie4t1o\nie4t1ö4\nie2tri\niet2se\ni1ett\nieu2e\nie1un\ni1ex\n2if\nif1ar\ni2f3arm\nif4at\nif1au\ni2fec\nife2i\nif2en\nifens2\nif1erg\nif1erh\nif2fl\nif3l\ni1f4la\nif4lä\ni1flü\nif3r\nif4ra\ni1frau\ni1fre\nif4rei\nif4rü\nif2s\nif3se\nif3sp\nif2ta\nift3erk\nif2top\nif4t3ri\nift3s2p\nift3sz\n2i1g\niga3i\ni2g1ang\nig1art\niga1s4\ni4gefar\nige4na\nig1erz\ni2g1im\ni2gl\nig1lä\nig4na\ni4gnä\ni3g4neu\nig4no\ni3go\nig4ra\nig3rei\nig4sal\nig3sä\nig4se\nig3so\nig3spr\nig3stei\nig4sto\nig4stö\nig3str\nig4stre\nig3stü\nigung4\n2i1h\ni2h1am\ni2har\ni3he\nihe1e\nihe4n\nih3m\nih3n\nih3r\nihs2\ni2h1um\nih1w\nii2\nii3a4\ni1ie\ni3i4g\ni1im\ni1in\ni1i4s\ni2is.\nii3t\ni1j\n2i1k\ni2k1a4k\nik1amt\ni2k1ano\nik1anz\ni4kanze\nik1art\nik3att\ni2k1au\ni2kär\n4ike\ni2k1ei\nike2l1\ni2k1e2r2e\nik1erf\niker6fah\ni2ker2l\ni2k1eta\ni3ki.\nik1in\ni2kind\ni2k3l\ni3kla\ni3k4lä\ni2kn\nik3no\nik2o3p4\niko3s\ni2köl\ni2k3ra\nik3rä\nik3re\nik1s2\nik3so\nik3sz\nikt2e\nikt3erk\nikt3r\nik2tre\ni2kun\ni3kus\ni1la\ni2l3ab\nil1a2d\ni2l1ak\ni2l3a2m\nil1ans\nil1asp\nil1au\nil4aufb\nil3aus\ni2laut\ni1lä1\n6ilb\nil2c\nil2da\nil4d3en4t\nild2er\nild1o\nil2dor\nil2dr\nil1e2c\nile2h\nil1ehe\nil1ein\nil1el\ni4lents\ni2l1erf\ni2l1erg\ni2l1err\nilf2\nil2f3l\nil2f3re\nilf4s3\nilie4n\nilig1a2\nili4gab\ni2l1ind\ni2l1ip\ni3lip.\ni3lips\n2ill.\nil3l2a\nil3l2er\nil3l2i\n2ills\nil2mak\nil4mang\nil2m3at\nil2mau\nil2min\n2ilo\ni2l1or\nil3t2h\ni1lu2\ni2lum\nilung4\ni3lus\nilv4\nil2z1ar\nilz3erk\n2im.\ni2manw\ni2m1arm\nim4at\nima2tr\nimat5sc\nima4tur\ni2meg\ni2mej\ni2mek\ni2mele\ni2melf\ni2m1erf\ni2m1erz\ni4mesh\ni2meti\ni2mew\ni2m1inf\ni2m1ins\nim2mei\nim4m3ent\n1immo\n2imo\nim1org\n1impo\nimp4s\nim3pse\n1impu\nim2st\nim3sta\n2imt\nimt3s2\n2imu\nin3a2c\ni4nack\ni2n1ad\nin2af\nin3am\ni3nap\nin2ara\nin2ars\nina4s\ni2n3au\nin1äs\nin2dal\nin2dan\n1index\nin3do\n2indr\nind4ri\nin3drü\n1indus\n2ine\ni2n1e2be\nin1ehe\nin3ei\ni2n1eng\nin3erbe\ni4nerbi\nin2erh\niner4lö\ni4ner4tr\ni4nesk\nin1eu\nine3un\nine2x\nin3f\n1info.\n1infos\n2inga\ning1af\nin2g1a4g\nin2gl\ning4sam\n1inhab\n2inhar\n2inhau\n4inhe\nin2i3d\ni3nie\n2inig\nini3kr\nin2ir\n2inis\nini3se\ni3nitz\n3inkarn\ninma4le\n2inn.\nin4n3erm\n2innl\nin2nor\ninn4sta\n1innta\n2ino\nin1od\nin3ols\nin1or\nino1s2\nino3t\ni1nö\nin1ö2d\n2inp\n2inr\nins2am\ninsch2\nin2seb\n2insen\nins3ert\nin3skan\nin3skr\n1insta\nin4s3tät\nin3stel\nin3su\n1insuf\nin4s3um\nin3s2z\n1integ\nint2h\nin3t4r\nin5tri\nin1u\ni3n2um\nin3unz\ninvil4\ni1ny\nin3zw\ni1ñ\n2i1o\nio1c\nio2d\ni2oda\nio3e4\niof4l\ni2o3h\nio2i3d\nio3k4\ni3ol.\ni3om.\ni3oms\nion2\ni3on.\nional3a\nio2nau\nion3d\ni3ons3\nion4spi\nion4st\ni2ony\ni2o1p\nio4pf\ni3ops\ni3opt\ni2or\ni3or.\ni3orc\niore4n\ni3orp\ni3ors\ni3ort\nio3s2\ni2ost\ni3ot.\ni3ots\ni2ou\ni2ov\nio2x\ni3oz.\ni1ö2k\ni3ön\ni1ös.\n2ip.\ni1pa\ni1pe\nipen3\ni3per\nip3fa\niph2\n2i1pi\nipi3el\nipi3en\nipi2s\nip4l\nip2pl\nip3pu\ni1pr\n2ips\n2ipu\n2i1q\ni1r2a\ni3rad\n1i2rak\nirat2\ni1rä\nir2bl\nir1c\nir2e\ni3ree\n2irek\n2i3ré\nir2gl\nirg4s\nir2he\nir2i\n2irig\n2irk\nir2k3l\nirli4n\nir2mak\nir2mau\nir4mä\nir2m1ei\nir2mum\nir4m3unt\nir2nar\nir2no\ni1ro\n1iron\niro2s\ni1rö\nirpla4\nirr2h\nir4sch3w\nir3se\nir3sh\nir2st\nirt2st\niru2s1\ni3sac\ni4s1amt\nis2ap\nis3are\ni2sau\ni2s1än\n2isb\ni2sca\nisch3ar\ni3s2che\ni4schef\ni4sch3e4h\ni4sch3ei\ni4schin\ni5sching\ni2sch1l\nisch3le\ni2schm\nisch3ob\nisch3re\nisch3ru\ni4schwa\ni6schwir\ni4schwo\nisch3wu\nis1chy\ni2s3cr\n2ise\nise3e\nise3ha\nise3hi\nise3inf\ni4seint\nise2n1\nis2end\nisen3s\ni2serh\ni2s1erm\niser2u\ni2s1ess\ni4s3etat\nis2has\nisi2a\ni2s1id\ni2s1of\niso6nend\nis1op\n3i2sot\nis1pa\ni2spar\nis1pe\nis1pic\nis2pit\ni2spro\nis3sa\nis4s1ac\nis4sau\nis4s3che\nis2st\nis3sta\nis3sto\niss3tr\nis3stu\nis2sum\nis3t\nis4tab\nis4tam\nist2an\ni1s4tat\nis4tel\niste4n\nistes3\ni1s4teu\ni1s4til\nis4toc\nis4tö\nis5tör\nist4ra\nist3re\nis4tü\nisum3p\ni2sü\ni1ß\niß1ers\nit1ab.\nital1a\nit1alt\nit1am\nit1an\nit2an.\nit3a4re\nit1art\ni3tat\nit1au\ni3tauc\ni4t1ax\n4itä\nit2är\ni2t1äs\nität2\ni2t1ei\ni4teig\nit2eil\ni4tein\n2itel\nite2la\nite4n\niten3s2\ni4tepo\ni2tex\ni5thr\ni2t1id\n1itii\niti4kan\ni2t1in1\nit2inn\nitmen2\ni5toc\ni2t1of\ni3tö\nit3raf\ni2t3ran\nit3ras\nit3rau\nit3räu\nit3re\nit3ric\nit3rom\nit4ron\ni3tru\nit3run\nit2sa\nits1a4g\nit2s1e4\nits3er1\nit2so\nit2s1pe\nit4staf\nit2sto\nit2teb\nit4tri\nitt2sp\nit1uh\ni2t1um\ni2tuns\nit1urg\nitut4\ni3tü\n2itz\nit2zä\nit4z3erg\nit2z1w\n2i3u2\nium1\ni1ü\n2i1v\ni2v1ak\niv1ang\ni2veb\ni2v1ei\niv1elt\nive4n\ni2v1ene\ni2v1ent\ni2v1ur\n2i1w\niwur2\n2i1x\ni2xa\nix2em\ni3xi\n2i1z\niz1ap\niz1au\nizei3c\nize2n\ni2z1ene\niz4er\ni2z1ir\nizo2b\ni2zö\ni2z1w\ní1l\nja1c\njah4r3ei\njahr4s\nja3l2a\nja3ne\njani1\nja1st\n2jat\nje2a\njean2s\nje1c\nje2g\njek4ter\njekto2\njektor4\njek2tr\nje3na\nje2p\nje4s3t\nje2t1a\nje2t3h\nje2t3r\njet3s2\njet3t\nje2t1u2\nje3w\nji2a\njit3\nji2v\njoa3\njo2b1\njob3r\njo2i\njoni1\njo1ra\njord2\njo2sc\njou4l\nj2u\nju2bl\njugen2\njugend3\nju2k\njung3s4\nju3ni\njur2o\njus3\njut2e1\n2j1v\n1ka\n3ka.\nk3a2a\nka3ar\nkab2bl\nka2ben\n2kabh\n2kabla\n2kablä\n2k1a2bo\nka3b4r\n2kabs\n2k1abt\nka1c\nk2ad\n2k3ada\n2k3a2dr\nka1f4l\nka1fr\nkaf3t2\nk2ag\nka1in\nka3ka\nkaken4\n2kala.\nka2lan\nka3lei\nka3len.\nka4lens\nkal3eri\nkal2ka\nkal2kr\nk1all\nkalo5\nkal4tr\nk3ama\nkamp8ferf\nkan2al\nka4n1a4s\nka2nau\nkand4\n2kanda\nkan2e\n2k1ang\nkan3k4\n2kanl\n2k1anna\nk1ans\nk2ans.\n6kantenn\nka3nu\n2kanw\nk2anz.\nka2o\n2k1apf\n3kara\n2karb\nk2ard\nk2arg\nka3r2i\nkari3es\nk2ark\n2k1arm\nk2arp3\nkar2pf\nk2ars\nkar3t\nk2arta\n2k1arti\nkaru2\nk2arw\n3kas\nka3se\nkasi1\nkas3s\nka2s3t\nka3tan\nka3t4h\nka4t3r\n2katt\nkau2f1o\n4kaufr\nkauf4sp\nk1aus\nkau3t2\n2kauto\n1kä\nk1äh\nk1ä2mi\nk1än\nkär2\nkä2s1c\nkäse3\n2k3b4\nkbo4n\nkbu2s\nkby4\n2k3c\n2k3d2\nkdamp2\n2k1e1c\nk1eff\nkefi4\nkege2\nke2gl\nke2he.\nkehr2s\nkehr4s3o\n2k1eic\n2k1eig\nk1ein\nke1in2d\n2keinh\nkei1s\n2k1eise\nkeit2\nke2la\nkel1ac\nke3lag\nkel1au\nke2lä\nkel3b4\n2ke2lek\nke2len\n2ke3let\nkell4e\nkel3s2k\nk4elt\n2k1emp\nk2en.\nken3au\n4ken4gag\n2kenlä\nke2no\nkens2k\nken5stei\nken3sz\nk2ente\nk3enten\nken3th\nk2entr\n2k1ents\nk2entu\n2kentw\n2keo2\nke2pl\nk2er.\nke1rad\nk2erc\n4kerfah\nk4erfam\nk3ergeb\nker6gebn\nk3er4hö\nke6rin6nu\nkerin6st\nkerin4t\nker4ken\nk2erko\nk2erl\nk3er4lau\nk3er4leb\nk6erlebe\nker4neu\nk1e2ro\nk2ers.\nkerz2\nker4zeu\n2k1er2zi\nk6es.\nke2sel\nke4t1a\nke2t3h\nket3s\nke1up\nkeu6schl\n2k1e2x\n2k3f4\n2k1g2\n2k1h4\nkho3m\nki3a4\nki1c\n2k1i2de\nki3dr\nki2el\nkie2l3o\nki1f4l\nki1f4r\nki3k4\n2kil2a\nki3li\nki3lo\nk2imi\nk2in.\nk2ing\n2kinh\nk2ini\nk2inn\nki3n4o3\nkin3s\n2k1inse\n2k1int\nki3or\nkio4s\n3kir\nkis2p\nkist2\nkis4to\n2kiz\nki3zi\n2k3j\n2k1k4\nkl2\n4kl.\n4kla.\nk4lar\n4k1last\nk2le\n4kle.\nkle3ari\n4kleh\nk4leid\n4k3leit\nk3lem.\n2k3ler\nkle2ra\n2k3leu\nkle3us\n2klic\n2klig\nk2lin\nk3lip\nk2lir\nk2lisc\n2klist\nklit2s\n4kliz\n2k3loc\nklo2i3\nk4lop\nklost4\nklö2s\nk2löt\nk1lu\nkluf2\nklung4\n2k1lüc\n2kly\n2k1m\nk2n2\n3knab\nk3ne\nk4nei\n2k5ner\nkno4bl\n2k5nor\nk3nu\n3knü\n1ko\nko2al\n2kobj\n2k1o2fe\nkoff4\nkoh3lu\nko1i2\nkol4a\nko3le\nkol2k5\n3kom\nko4mu\nk2on\nko3n2e\nkon3s4\nko3nu\n2kop.\nko1pe\nkop4fen\n2kops\n2kopz\nko1r2a\n2k1orc\nkor6derg\nko3ri\nk2os\nko2sp\nko2st\nko3ta\nkot3s2\nkot4tak\n2k1ou\n3kow\nko2we\nk1o2x\n1kö\nkö2f\nk1öl\n2k1p2\nk1q\nk2r4\n2k3rad\nk3rats\n2kraum\nk4raz\n2k3rät\n2k3räum\n2kre.\n2k3rec\n2k3rede\n2k3ref\n2kreg\nk3reic\nkre1i2e4\nkreier4\nk3reih\n2k3rh\n2krib\n2k3ric\nk3ries\n2krip\n3kris\n3k4ron\n2kruf\nkrü1b\n2ks\nk4s1amt\nk2san\nks3ar\nk2sau\nks2än\nksch4\nks1e2b\nk2s1em\nk2sent\nks1erl\nk2s1ers\nk2s1erw\nks3ha\nk2s1id\nk2s1in\nk2s1o2\nk3sof\nks1pa\nk3spe\nks2por\nks2pu\nks3s2\nkst4\nk1sta\nk4s3tanz\nk3stat4\nk1ste\nk1s2ti\nk1sto\nk2stor\nk1str\nk2strä\nk1stu\nk2stum\nk2s1u\nks2zen\n4k1t\nk2t1ad\nkt1akt\nk3tal\nkt1am\nkt1an\nk2t3a2r\nkta4re\nk2t1au\nktä3s\nkte3e\nkt1ei\nk2temp\nk2tent\nk4t3erfo\nk2t1erh\nkte3ru\nk2tex\nk2th\nkt3ho\nk2t1id\nkt1im\nk2t1ing\nkt1ins\nkti4ter\nk2t1of\nk3top\nkt1ope\nk4torga\nkt3orie\nkt4ran\nkt3ras\nk4tref\nkt4ro\nktro1s\nkt3run\nkt3s4\nktt2\nk2tuns\nk3tü\nkt3z\nku1c\nkuh1\n2k1uhr\nkul2a\nku3l2e\nku3l2i\n4kulp\n2k3uml\nkum2s1\nk2u3n2a\nkung4\nkun4s4\nkunst3\n2kunt\n2k1up.\nkur2bl\nku2rei\nkuri2e\nkuri4er\nku2ro\nkur2sp\nkur2st\nku4schl\nku2sp\nkus3t\nku2su\n1kü\n2küb\nkü1c\nkür4s\n2k1v\n2k1w\n2k3z2\nkze3l\n3la.\nla3ba\n2labb\n4l3aben\n2labf\n2labg\n2labh\n4l1a2bl\nlab2o\nl2abr\nlab4ra\nlab4ri\n2l3abs\nl1abt\n3labu\n2labw\nla1ce\nla2ce.\n1lad\nlad2i\nl1adl\n2ladm\n2l1a2dr\n3ladu\nl1adv\n2laf\nla2fa\nlaf3s\nlaf3t\nla2ga\nla2gio\nla2gn\nlago2\nla2g1ob\n2la1ho\n1lai\nla2k1i\nl2akk\nla1k4l\n2l1al\n4lall\n4lalp\nl2ami\nla3min\n1lammf\nl2amp\n2l1amt\nlamt4s\nla4mun\nl1anal\nla2nau\n2lanb\n3l2and\nlan2d3a2\nlan6d5erw\nlan6d5erz\nlan2d3r\n2lanf\nlan2gl\nlang3s4\n2lanhä\nl2anhe\n2lanl\n4lanli\n2l3ann\nl1anp\n2lans\n4lansä\n2lantr\nlan2zw\n3lao\nl1a2po2\nlap4pl\nla2r1an\nla2r1ei\nla4rene\n3l4ar3g\nlar3ini\nlar3s\n2l1ar3t\nl3arti\nla2ru\nla2sau\n4lasd\nla3se\n2lash\n2lasi\nla2so\n2lasp\n3lasser\nla2st\nlast1o\nlat2a\nla3te\nla4tel\n2l3ath\nla2t3ra\nlat2s\n2lat2t1a\nlat4tan\nlat4t3in\nlat2t3r\nlaub4se\nl2auf.\nlau2fo\nl2aufz\n1laug\n2lausl\n2lausr\n2l1auss\n2lauto\n1law\nlawa4\nlay1\nlä1c\n1läd\n2läf\n2l1ähn\n1länd\nlär2m1a\nlä2s1c\n4lät\n2läub\n2läuc\n2läue\n1läuf\n1là\n2l1b\nl3bac\nl2b1ede\nl4beta\nl2b1id\nl2b1ins\nlb2lat\nl3blä\nlb3le\nl2bli\nl3blo\nl4bre.\nlb3rit\nlb2s\nlb3sa\nlb3se\nlb4sk\nlb3sp\nlbs6t\nlbst3e\nlb4sto\nlb2u\nl2b3uf\nlbzei2\n2l1c\nl3che\nl3chi\nlch3l\nlch3r\nlch3ü\nlch1w\nl3cl\n4l1d\nld3a2b1\nl3d2ac\nld3a2ck\nl2d1a2d\nlda4g\nl2d1ak\nld1al\nl3dam\nld1amm\nl2d3a2n\nl2d1a2r\nld3ari\nl3das\nl3dat\nld1au\nld1är\nl2dei\nl2dele\nl3der.\nld1erp\nl2d1e2se\nl2dex\nl2d1id\nl2d1im\nldo2r\nld2os\nld2ö2\nld3r\nl2dran\nl2dre\nl3d4ru\nld4rü\nld3sa\nld3st\nldt4\nld3th\nl2d1um\n1le\n3le.\nle2ad\nleben4s3\nle2bl\n2lec\nle2chi\nlecht4e\n3led\n4ledd\nle3de\nle2e\nle3ei\nlef2a\nle2g1as\nle2gau\nle2gä\nle2gl\nleg4r\n3leh\nleh3r2e\n4lehs\n4leht\n3lei.\nlei2br\nl2eic\nl2eid\n4l1eig\nl2ein.\nl2eind\nlein4du\nl2eine\nlei6nerb\n2leink\nl2eint\nleis6s5er\nl4eist\nlei4ßer\nl2eit\nlei2ta\nlei8t7er8sc\nleit3s2\nlekt2a\n2lektr\n3l2ela\n2le2lek\nlel3s\n3lemes\nle2m1o2\n4lemp\nlem3s\nl2en.\nle4nad\nle2nä\n4lendet\n2lendu\nle4n3end\n4lenerg\nl2enf\nle3ni\nl2enk\n2l1enni\nl2e2no\nlen4sem\nlen3sz\nl1ents\n2l3entw\nlent4wä\n5lentwet\n4lentz\nlen2zi\nle1os\n2lep\n3lepa\n3lepf\nlepositi8\n3lepr\nl2er.\nl2e1ra\nle2ra4g\nle2rau\nlerb4\n4l3ereig\nle4r3eim\nle4rers\nl1erfo\nl2erfr\nl2erfü\n3lergeh\nl3ergen\n3l4ergew\n2l1ergi\nlerin4s\nlerk2\nl2erka\nl2erko\nl2erle\n2l1er2ö\n3l2erra\nl4ers.\nlers2k\nlers2t\nler3t\n6lerwerb\nl1erz\nl2erza\nles2am\nles2e\n2l1esel\nle3ser\nle3sh\nlesi1\nle3sk\nles2t\nleste3\nle1sto\n4lesw\n2lesy\nle2tat\n2le3th\n2leto\nlet4tu\nle2u\n4leud\n2leuro\n3leut\n3lev\n2lexe\nle2xis\n2lexz\n2l1f\nl3fah\nlfang3\nl2f1ec\nlfe1e\nl4feis\nl3f4lä\nlf3lo\nl3f4lu\nlf3ram\nlf2tr\nlf4u\nlfur1\nl3fü\n2l1g\nlga3t\nlgd4\nlgen2a\nlge3ra\nlgeräu3\nl2geti\nl3go\nlg3re\nl3gro\n2l1h2\n3lhi.\n1li\n3lia\nli3ac\nli3ak\nli3ar\nlia1s\nlibi3\nli1c\n3lichem\n3licher\nli3chi\n4lick\nli2cka\nli3d2a\nli2deo\n2l1ido\nli4ds\nlid3sc\nl2ie\n3lie.\nliebe4s\nli3ene\nlien3s\nlie2s3c\nlie2st\n3lig\nlig4n\nli2gre\nli3ke\nli2kr\nlik2sp\nlik4ter\nli3l\nlil2a\n2lim\nli3m2a\n3limo\nli3n2a\nlin3al\n2l1indu\nli2nef\nli2neh\nli2nep\nli2nes\n2l1inf\nlings5\n2l1inh\n2l1in1it\n2l1inj\nlin2k1a\nlink2s\nli2nol\nl2ins.\nl2insa\nl2insc\n2linsp\n2linst\n2l1int\nl1inv\n2linz\nli2o\nli4om\nli3os.\nli2p3a\n3lis.\nli3s2a\nli4schu\n4lish\n2l1isl\n2l1i4so\nli2sp\nliss2\nlit2a\nli2tal\nli3te\nlit2h\nlit1s2\nlit3sz\nli3tu\n3liu\n2lixi\nli2za\nlizei3\n4l1j\n2l1k\nlk1alp\nl3k2an\nl3kar.\nlken3t\nlk2l\nlk3lo\nl3k4lu\nlk4ne\nlkor2b1\nlk4ra\nl2k3ro\nl2k3ru\nlk2s1\nlk3sä\nlks3t\nlk4stä\nl3k2ü\n4l1l\nll1abb\nll1a2be\nl2labt\nll1aff\nll1akt\nl3l2al\nl2l1a2m\nll3ama\nlla2n\nll2anw\nll1anz\nl3lap\nll1arm\nll1au\nll3aug\nl2laus\nl2l1äm\nllb4\nllch4\nll3d4\nll1ech\nlle3en\nl2l1ef\nll1eim\nll2em\nl3len.\nlle4n3a\nll3endu\nllen3g\nl4lents\nl3ler.\nlle2ra\nl4lerfo\nl6lergen\nl4lergo\nll3ernt\nll3ertr\nl2lerz\nll2es\nl2lex\nllg4\nll1imb\nll1imp\nl2l1ind\nll1ins\nllk4\nll3l2\nll5m\nlln2\nll1ob\nl2lobe\nl2l1of\nll1opf\nl2l1o2r\nl3lor.\nl3lore\nl2l1ou\nl3low\nl2löf\nll1ö4se\nll3sh\nll3s2k\nll2spr\nll5t4\nllti2m\nllt5s2\nllu2f\nll1ur\nllus5t6\nll3z2\n2l1m\nl2m3a2b\nl2marc\nlm1aus\nlm1c\nlme2e\nlm3eins\nl2m1e2p\nl2m1erz\nlm1ind\nlm1ins\nl2möl\nlm3p\nlmpf4\nlms2t\nlm3ste\nlm3s2z\nlm3t\n4ln\nlna4r\nln3are\nlnd2\nl3n4e\nl3ni\nl1nu\nl1nü\n1lo\n3l2ob.\nlo2ber\n2lobj\n2l1o2bl\nl2obr\nlob4ri\nl1o2fe\nlo1fl\nlof4r\nlo2gau\nlo3h2e\n2l1ohr\nloi4r\n3lok\nlo2k3r\nlol2a\nl1o2ly\nlo2min\nlo2n1o\nlo2o\n2lopf\n2lopt\nlo1ra\nlo4rä\n2lorc\nl1ord\nlo3ren\n2l1or3g2\nlo3ro\n3lorq\n3los.\nlo4sa\n3lose\nlo4ske\nlo2spe\nloss2e\nlo4ste\nlos3t4r\nlo2ta\nlo3tha\nloti4o\n2l1ov\nlo2ve\n2lox\n1lö\nlö2b3\n2löd\nlö2f\n2l3öfe\n4lög\nl1öhr\n2l1ö4l3\n4löß\n2l1p\nl3pa\nlpe2n3\nlp2f\nl2p1ho\nlp3t4\nl3pu\n2l1q\n2l3r2\nlrat4s\nlre1s\nlrut4\nlrü1b\n4l1s\nl3sac\nl2s1a2d\nl3s2al\nl4s1amb\nl2sann\nl3sare\nl2sau\nl4schin\nl4schmü\nl2s1e2b\nl2s1ec\nl2s1em\nls1ere\nls1erg\nl2serh\nls1erl\nl2s1ers\nl2s1erw\nl3sex\nl4sha\nlsho2\nl2s1imp\nls2log\nls3ohne\nl4s3ort.\nl3s2pi\nls2po\nl2spro\nl3s2pu\nls3s2\nlst2a\nlstab6\nls4taf\nl4s3täti\nl2ste\nl3stec\nl3stei\nl3stel\nl4stem\nls6terne\nls6terns\nls2tie\nl2stit\nls4tr\nls2tu\nls1um\nl2sun\nlsu3s\nls2zen\n4l1t\nl2tab\nltag4\nlt1ak\nlt1a2m\nl4t3ame\nlt3and\nlt1ang\nl3tarb\nlt1art\nl2t3ato\nl2t1au\nlt1eh\nl2t1eis\nl4te4lem\nlt3eli\nlt2en\nl5ten.\nlter3a\nlt2erg\nlt4erö\nl4t1e4sk\nlte2th\nl2t1eu\nl2th\nl4thei\nlt3ho\nl3thu\nltimo4\nl2tob\nl2t1of\nlt1op\nl2t1o2ri\nlto2w\nlt1öl\nl3tör\nlt1ös\nl4t3öt\nltra3l\nl3trä\nlt3räu\nlt3re\nlt4rie\nlt3roc\nlt3ros\nl2t3rö\nl6ts\nlt3sc\nlt2so\nlt4stab\nlt4stoc\nltt2\nlt1uh\nl2t1um\nltu4ran\nltu2ri\nl3tü\nlu1an\n4lu4b3\nluba2\nlubs2\nlu2dr\nlu2es\n1luf\n2l1ufe\n2luff\nluf2t1a\nluf2t1e\nluf2t5r\nlu2g1a\nlu2g1e2b\nlu4g3l\nlu2go\nlu2g3r\nlug3sa\nlug3sp\nlu2gu\n2l1uh\nlu1id.\nlu1is.\nlume2\n2lumf\n2luml\nl2ump\nl1ums\nl1umw\n1lu2n\n2l1una\n2l1unf\nlung4sc\n2l1uni\n2lunt\n2lunw\n4luo\n2lur\nl1urn\nl1urt\n2luse\nlu2sp\nlus4s3a\nlus2s1c\nluss3er\nlus6serf\nlus6serk\nlus6sers\nlus2s1o\nlus2s3p\nlus2s3t\nlus4stä\nlu4st\nlus4t1a\nlust3re\nlu2s1u\nlu2t1a\nlu2tä\nlu4teg\nlu4t3erg\nlut1o2f\nlu2top\nlu4t3r\n3lux\n2lüb\n5lüd\nlüh1l\n2l1v\n2l3w\n2lx\n1ly\nly1ar\nly3c\n2lymp\n3lyn\nly3no\nly1o\nly3u\n2l1z\nl2z3ac\nl3z2an\nlz2erk\nlz1ind\nlzo2f\nl2zö\nlz3t2\nl2z1u4fe\nlz1w\nlz2wec\n1ma\nm1ab\nm2abe\n2mabk\nm2ab4r\n2mabs\n2mabt\nmach4tr\nma2ci\nma3da\nma2d4r\nma4d2s\nmae2\nma1f\nma2ge.\nma2geb\nma2gef\nma2geg\nma2gek\nma2gep\nma4ges.\nma2get\nma2gev\nma2gew\n2m1agg\nmagi5er.\nmagi5ers\nma3g4n\n2m1ago\nmai4se\n2m1akt\nmal1ak\nma4lakt\nma2lan\nma4l3at\nma2lau\nmal3d\nma3ler\nmali1e\nmal3lo\n2mallt\nmalu4\nma2l3ut\nmam3m\n2m1anal\nma2nau\n2manb\nman4ce.\nman3d2\nman3ers\nma2net\nm2anf\n2m1angr\nm2anh\n2manl\nm4ann\n2mansa\n2mansä\n2mansc\n2mantw\n2manz\nma2or\nm2app\n2marb\nmar3g2\n4ma3r2o\nmaro3d\n4marr\nmar6schm\nmar6schr\nma3r2u\nm1arz\n3mas\nma3s2pa\n4m1aspe\nmassen3\nmas4tel\nma1s4tr\n3maß\nma2ta2b\nma2tan\nmat4c\nma2tel\nma4t3erd\nma5tri\nmat3se\nmat3sp\n2m1au2f\nma3un\n2mausg\nm4ay\nma1yo\n3mä\nm1ähn\nmä1i2\n4m1änd\nm1ärg\nmä3t4r\nmäu2s1c\n2m1b2\nmbe2e\nmb4l\nm3b4r\nmby4\n2mc\nm3ch\n2m1d\nmd1a\nm2d1ä\nm2dei\nmds2e\nm2d1um\n1me\nmeb4\nm2e1c\nmedi3\nmedie4\nmedien3\n2medy\nme1ef\nmee2n1\nmega1\n3meh\n2m1eif\n2m1eig\nm2eil\nmein4da\nme1i4so\n3meist\nme3lam\n3meld\nme2lek\nme2ler\nmelet4\n2melf.\nmell2\nmel2se\nmel5t4\n6mel6tern\n2m1e2mi\nm2en.\nmena2b\nme3nal\nmen3ar\nmen3au\nmen3gl\nme3nor\nm2ens\nmen4sk\nmen2so\nmen3ta\nmen6tanz\n2mentn\n4m3entwi\nme1o\n2meou\n2meö\n3mer.\nme1ra\nme2r3ap\nme4rens\nmer2er\n4m3ergän\n3merin\nmerin4d\nmerin4t\nme2ro\n3mers\nmerz4en\n3mes\nmes1a\nme2sal\nme4sä\n4meser\n2me3sh\n4m1essa\nmes6serg\nmes2s1o\nmes2s1p\nmes2st\nmeste2\nme1sto\n4mesu\nme3t2a\nme3th\nmeu1\n2m1ex\n1mé\n2m1f4\nmfi4l\n4m1g2\n2m1h4\n1mi\nmi2ad\nmi3ak\nmibi1\nmi1c\nmi3da\nmie3dr\nmi2e1i\nmie3l\nmien3s\nmi2er\nmierer4\nmie2ro\nmi4et\nmie4ti\n3mig\nmi2kar\nmi2ki\nmi2ku\n3mil\nmi3l2a\nmilch1\nmil4che\nmild4s\n4milz\n2m1imp\nminde4s\nmin2en\nmin2eu\nmin2ga\nming3s4\nmi3ni\n3min2o\nmi1nu\n3mir.\nmi3ra\n3miri\n3mirs\n3mirw\nmi2sa\nmi4scha\nmi4schn\nmi4schw\nmise1\nmis2s1c\nmi2s5te\n3mit\nmi2ta\nmi2th\nmi2t1r\nmit3s2\nmit5sa\nmi5tsu\nmi2t1u\n4mitz\n2m1j\n4m1k4\nm3ka\nmk5re.\n4m1l2\nml3c\nml3l\nml3s\n2m1m\nm2mab\nm2m1ak\nm2m1al\nmm1ang\nm2m1ans\nmm1anz\nm2m1au\nmmd2\nmm1ei\nmme4lin\nmme4na\nm4mentw\nmme2ra2\nmme4rec\nmme2sa\nmm1inb\nmm1inf\nmm1inh\nmm1ins\nmm1int\nmmi3sc\nmmi1s4t\nmmm2\nmm3p\nmm2s\nmm3si\nmm3sp\nmm3sta\nmm3str\nm2mum\nmm2un\nmmül2\nmmüll1\n2m3n2\nm4nesi\n1mo\nmoa3\n2mobj\n3m2od\nmode3s\nmo2dr\n4mog.\nmo2gal\n3moh\nmo2i3\nmo2k1l\n2mol.\n3mom\nmom2e\n3m2on\nmo3ne\nmo4n1er\nmon2s3\nmon3su\n3mo2o\n2m1ope\n2mopt\nmo1ra\nmo2rar\n2m1orc\nmor2d3a\nmor2dr\nmo2rer\nmorgen5s6\nmork4\n3mos\nmos4ta\nmoster4\n3mot\nm1o2x\nmo1y\n1mö\nmö2c\n4mök\nm1öl\n2m1p\nm2pf\nmp4f3erg\nmpf3erp\nmpf3err\nmp4f3erz\nmp2fl\nmpf3li\nmpf1or\nm3pon\nmp3ta\nm3pu\n2m1q\n2m3r2\n2m1s\nm2san\nms3and\nm4sap\nms1as\nm2sau\nm3sä\nm3sc\nmsch2\nm4sco\nm3se\nm4s1ef\nms1erw\nm4sex\nms1ini\nmso2r\nms1ori\nm2spä\nm2sped\nms2po\nm2spot\nm2spro\nms2pu\nms3s2\nm4stag\nm3stel\nm3s2ti\nm3sto\nms4tr\nms5trä\nms5tren\nm3s2tu\nms4tü\nms1um\nm2sü\nm3sy\n4m1t\nmt1ab\nmt1ak\nm3tam\nmt1ar\nmt3are\nmt1elt\nm2t1erf\nm4t1erg\nm2t1erl\nm2t1ers\nm2t1ert\nm4t1eta\nm2t1eu\nm2th\nmt3ho\nm2t1im\nm2t1ins\nmti2s\nmtmen2\nm3tö\nmt1ös\nm4ts1\nmt2sa\nmt2se\nmt3s2ka\nmt2spr\nmtt2\nmt1um\nmt1urt\nm3tü\nmt3z\n1mu\nmu1a\nmu3cke\n2m3uh\nmu3la\n2muls\n3mun\nmun2d1a\n4m3unf\n4m3ungeb\nmu3ni\nm4unk\nmunt2\n4munz\nmu3ra\nmu4r1u2f\nm4us\nmu4s1a\n3musi\nmu2s1o\nmu2sp\nmus3t\nmu2su\nmut1au\nmuts3\nmut2st\n1mü\n2müb\nmül4len\n3mün\n3müt\nmütter3\n2m1v\nmvoll1\n2m1w2\nmwa2\nmwa4r\nmwel4\n1my\nmy4s\n2m1z\n1na\n3na.\n2n1ab\nna2bä\n4nabg\n4nabh\nna2bl\nn2abo\nna2br\n4n3abs\n4nabt\n3n2ac\nna2ch1\nna3chen\nnach3s\nnacht6ra\n4nadd\nn2ade\n4na2dr\nn1af\nna1f4r\n3n2ag\nna2gem\n3n2ah\nna2h1a\nn3ahn\n3nai\nnai2e\nn3aig\nn3air\n2n1ak\nna2ka\n3nako\nn2al.\nna2l1a2\nna2lä\n3n2ald\nn4ale\nna4lent\nna2let\nnal3la\nnalmo2\nna2lop\nnal2ph\nn2als.\nnal3t4\nna2lu\n2naly\nn4am.\n3name\nn4amen\n4n3a2mer\nna3m4n\n3namo\n2n1amt\nnamt4s\nn1an.\n4n1a2na\n4nanb\nn1and2\n4n1ang\n2nanh\n2nani\n4nank\n2nanl\n3nann\nna3no\nn1anp\n2nanr\n2n1ans\n2nantr\n2nanw\nnap2si\nn1ar\n5nar.\nna2r1a\n2narc\nn2ard\n4narg\n3nari\nn2ark\nn2arle\n2narm\nn2arp\n4n3art\nna3r2u\n3nas\nn2as.\nna4schw\n4nasp\n4n1a2sy\nnasyl2\n3nat\nn4ata\nna3t4h\n4natm\nnats1\nnat4sa\nnat4sc\n4natt\nn1au\n4nauf\nnauf4fr\nn3aug\n5naui\n3n2aul\n4nausb\n4nausg\nn2auso\n4nauss\nn4auste\n4nausw\nnavi5er.\nnavi5ers\n1nä\n3n2äc\n3näe\nn1ähn\n2n1ä2m\n2n1än\nnär4s5\n3näs\nnä2sc\nn2äss\n2näu\n3nä1um\n2n3b4\nnbe2in\nnbe3n\nnbe3r2e\nnbes4\nnbu2s\nnby4\n2n1c\nn3ce2n3\nnch3m\nn2ck\n2n1d\nnd2ag\nn2d1ak\nn2danl\nnd1ann\nn2d1anz\nndat2\nnd1au\nnd1c\nnde4al.\nn2dei\nnde4län\nn4d3ents\nnde4rob\nnder5ste\nnde2se\nndi2a3\nn2dob\nndo2be\nndo1c\nnd1op\nnd1or\nn2dö\nn2d3rat\nn2d3re\nn2drob\nnd3rol\nnd3ros\nn2drö\nn2drui\nn4d3run\nnd2sor\nnd2spr\nnd4stab\nnds3tau\nnd3th\nndt4r\nn2dü4\nndy3\n1ne\n3ne.\nne2ap\nne3as\nne3at\nne2bl\n2n1ebn\n2nec\n3neca\nne1ck\n3ned\nne2de\n2nee3\nne2e2i4\nne3ein\nn1ef\nneg4\n2ne2he.\n3nehm\n4n1ehr\n2n1ei\nn2eid\n4neif\n3neigt\n4n3eing\n4n3eink\nne2ke\nnek3t4\nne2l\n3nela\nnel3b\n2n1ele\n4nelek\n4nelem\nne3len\nne3li\nnel4la\n3ne3l2o\n3ne3lu\nn2em.\n2n1emb\nn1e2mi\n2n3emp\n2n1ems\n3nen\nn4en.\nnen3a2\nn2enb\nn2enc\n4n1endb\n4n1endd\n4n1endf\nn1endg\n4n1endh\n4n1endk\n4n1endp\n4n1endt\n4n1endw\nne2n1e2b\nnen3ei\nnenen1\nne4nene\n4nengb\nnen4ge.\nnen4gen\n4nengs\n4nengt\nn2enh\nne2ni\nn2enj\nnen3k\nne2no\nn2ens\nnens4e\nnen3sk\n5n2en3t2a\nn1entb\n4n1entl\n4nentn\n5nentr\nn1ents\n4n3entw\n4nentz\nne2n3u\nn2env\nn2enw\nne2ob\nne1os\n2nepf\n2n1epo\nne2pos\nn2er.\nne1ra\nne2ra2b\nne3r4al\nne2r3am\nne2ran\nne2rap\nne2rau\n4nerbe.\n4nerben\nn1erbi\nnere2\nne2reb\nn1erf\n4n5erfo\nnerfor4\n2nerfü\n3nergr\nn1erh\n2n3erhö\n3neri\nn1erk\nn2erli\n2n1erlö\nn1ermä\nner4mit\nn2ern.\n4n1ernt\nne2ro\nne1rös\nn2erp\n3n2ers.\n2n3ersa\nner8schle\nn2ert.\nn1ertr\nne2rup\nn2erv\n2n1erz\n3n2es\nn4es.\nne3san\nnes4c\nnesi1e\nne3ska\nnes1o\nne2s1p\n4n3essi\nne1sta\nnes3ti\nne2tad\nne2t1ak\nne2t1an\nne2tap\nn1etat\nne2tau\nne2th\nnet3ha\nnett4sc\nn1e2tu\nnet2zi\nne2u\nneu1c\nneu3g\n2n1eup\nn2ew\n2n1ex\n3nez\n1né\n2n1f\nnf1ak\nnfalt4\nnf2ä\nnff4\nn3fi\nnfi4le.\nnf4l\nnf5lin\nnf2o\nnfo1s\nnf4r\nnf3s\nnft2o\nnft4s3\nn2f1u\n4n1g\nng2abs\nn2g1ac\nng1ad\nn2g1ak\nn2g3a2m\nn2g1and\nng2anf\nng1anz\nn2g1äl\nng3d4\nn3gef\nn2g1ein\nng2en\nngen2a\nn3ger\nnge4ram\nn4g3erse\nnge4zän\nng3g4\nng3hu\nn2g1i2d\nn3gläs\nn2glic\nn2glo\nn3g2loc\nn2glö\nng3m\nn2gn\nng3ne\nng1or\nng3rat\nng3roc\nngs3c\nng4s3e4h\nngs3pa\nngs5tri\nng3ts\nn2gum\n2n1h4\nn3han\nn3har\nn3hau\nn3hä\nn3he\nnhe2r\nn3hu\n1ni\n3nia\nnib4l\nnich1s\nnich8ters\nn1id\nni2de\nni3de.\nni3dr\nn4ie\nnie3b\nni1el\nnie3l2a\nnie4n3\nni3ene\nni1ero\nnifes3\nnig2a\n2n3i2gel\nnig3r\nni2gre\nnig4sp\n3nik\nni2kal\nni2kar\nni3ker\nni4k3ing\nni3kl\nni2kr\n3n2il\nnim2o\n4n1imp\nnin1\n3n2in.\nn2in4a\n4n3ind\n2ninf\n3n2ing4\n4n1inh\nni2nor\n2n1ins\nn2ins.\n4ninse\n4n1int\n2n1inv\nni2ob\nni3ok\nni3ol\nn2ip\nni3ra\n3n2is\nni4schw\nni2s1e\nni2s1p\nni3spi\nnis3s4\nni2s1u\n2nit\nni2ti\nni3t4r\nnit4s\nni3tsc\nnitts1\nnitt4sa\nni3tu\nni3v\n3nix\nn1j\n2n1k\nn2k3ad\nn2k1ak\nn3k2al\nn4k3alg\nnk2am\nn2kans\nn2kaus\nn2käh\nn2k1är\nnke2c\nn4k3erfa\nnk4erg\nnk1inh\nn2k1ins\nnk3len\nnk3les\nn2klie\nnk2lo\nnk2lu\nnk3lun\nnk4na\nn2kne\nn2k1ort\nnk2öf\nn2köl\nn2k3ro\nnk2s1al\nnks2ei\nnk3s2z\nnk2tak\nnk2tan\nnkt1it\nnk4top\nnk2tru\n2n3l2\n2n1m4\nnmen2s\n4n1n\nnna2be\nn2nada\nn4n1all\nn2n1an\nn2nau\nnnen3g\nn4nents\nnn2erh\nnn2erk\nnne2rö\nn4n3er4wa\nnner2z\nnne2s\nnnes1e\nnne4st\nnn2ex\nnn3f\nnng4\nn3ni\nn2nof\nnn1o2r\nnn3sc\nnn3se\nnn3s2p\nnn2th\nn2n1uf\nn2n1unf\nnn1ur\n1no\n3no.\n3nobl\nno2bla\nn2o3ble\n2n1ob2s\nno1c\n2no2d\nno3dr\nn1of\n2n3o2fe\nn3ole\nno2leu\nn2on.\n3n2opa\n3nor.\nnor2a\nno2rad\nno1rak\nno3ral\n2norc\nnor2d5r\n3norh\n3norm\n3nors\nn1ort\n3n2os.\nno3sh\nno2sp\nn2oste\nnost1r\n2nostv\nno3tab\nno2tä\nno4t3ei\nno2tel\nno3t3h\nno4tha\nno2t3in\nno2t1op\nno2tr\n3nov\n3now\n2n1o2x\n3noz\n2nöd\n2nö2f\n4n1ö4l\n2n3p4\nnpa2g\nnpro1\nnpsy3\n2n1q\n2n3r2\nnräu3s\nnre3sz\nnrö2s1\n6n1s\nn2s1a2d\nn2s1all\nn2sang\nn2sant\nn2saus\nn3sav\nn2s1än\nn2s1äus\nns2ca\nn6schef\nn4schro\nnsch7werd\nns1eb\nns1e2d\nnseh5ere\nnsen4sp\nns1ent\nn2s1ep\nns1erf\nns1erg\nn2serh\nn2s1erk\nn2s1erö\nns1ers\nn2s1erw\nn2s1erz\nnse2t\nn4s1eta\nn3sex\nnsfi4l\nnsho2f\nn3sil\nn2simp\nn2s1ini\nnsi4te\nnsi2tr\nns2kal\nn2s1op\nn4s3ort.\nnsp4\nn2spat\nn2sph\nn3s2pi\nns4pie\nn2spo\nns3pon\nn2sprä\nn4s3prie\nn4spro\nnsrü2\nns3s2\nnst1ak\nn3star\nn3stat\nn4stat.\nn4s3tate\nnst3eif\nn3stemm\nns4tent\nns6terbe\nn5s6terne\nn5s6terns\nnst4erö\nns2ti\nnst5opfe\nns4tor\nn4strac\nn4strie\nns2tu\nnst2ü\nnstü1b\nn2sty\nns2um\nn2s1un\nns2ung\nns4unr\nns4uns\nn3sy\nn4s3zi\n2n1t\nnt3abs\nn3t2a3c\nn3t2al\nnta3m\nnt1ang\nn4tanza\nnt2arb\nnt1ark\nnt2arm\nnt4at\nnt1äm\nn2t1äu\nnte3au\nnte2b\nnt1ebe\nnte1e\nnte3g6\nnt1eh\nn2teig\nnt2en\nnt4ene\nnten6te.\nn3ter\nnt4ern\nnt4ers\nnt4ert\nn4t1ess\nnteu3\nnte3v\nnt2her\nn2t3ho\nn3thr\nn3t4hu\nnti3c\nnti3k4l\nn2tinf\nn2t1inh\nntini1\nnt2ins\nn3ti1t\nntmen2\nntmo2\nn3to\nnto3me\nnton2s1\nn3tö\nnt3rec\nnt3reif\nn5trep\nnt4rig\nn5trop\nn2t3rü\nn4ts\nnt3sa\nnt4sau\nnts2o\nnts2p\nnt4s3par\nnts2t\nnt2sto\nn3tu\n3n4tu.\nntum4\nntu2ra\nntu4re.\nntu4res\nn3tü\nnt3z2\n1nu.\n1nu1a\nnu3ar\nnubi1\n1nu1c\n1nud\n3nue\nnu2es\nnuf2\nnu2fe\n1nug\n2n1uh\n1nui\nnu3k4\nn2um.\n2n3umb\n2numf\n2numg\n3numm\n2numr\n2n1ums\n2n3umz\nnu2n\n2nuna\n1n2ung4\n3nung.\nn3ungl\n2n1uni\n2nunt\n1nuo\n2nup\n2nur\n3nu2s\nnu3sc\nnu3se\nnu3sl\n1nut\nnu2ta\nnu4t3r\n1nuu\n1nux\n1nuz\n3nü.\n2nü4b\nnür1c\n3nüs\n1nüt\n2n1v2\nn3ver\n4n1w\n1ny.\n1nyh\n2nymu\nn1yo\n1nyr\n1nys\n1nyw\n2n1z\nn2zad\nn2z1a4g\nn2zan\nn2z1au\nn2z1än\nn2zär\nnzdi1s\nnz1ec\nn4zense\nn4zentw\nn4zentz\nnz3erwe\nnzi2ga\nnzig4s\nnz1ini\nn2zor\nnz2öl\nnz3s\nn2zurk\nn2z1wa\nn2z1wä\nn2zwö\nn2z1wu\nño1\n2o3a2\no4abi\no4ac\noa3che\noa3chi\no4ad\noa3de\noa4g\no4ah\no4a3i\noa3ke\noa4k1l\no4a3la\no4a3mi\noanne4\no2ar\no2as\n3oa3se\no4at\no5au\no1b\nob2al\n2oban\no3bar\n2o3b2ä\n2obb\nob2e\n2obe.\n2obea\nob3ein\n2o3b4en\noben3d4\noben3se\nober3in4\nobe4ris\n2obew\n2o3b2i\nobi4t\nob3ite\n1obj\nob1l\no2b3li\n2o3blo\n2o3bo\no2b3re\no3bri\nob3s2h\nob3sk\nobs2p\nob2sta\n2o3bu\nobu2s\n2o3bü\n2oby4\n2oc\no3ca\noc1c\no1ce\noch1a\nocha2b\no1che\noche4b\no2ch1ec\noch1ei\nocher4k\noch3l\noch3m\noch1o\noch3ö2\noch3r\noch1s\nocht2\noch3te\no1chu\nochu2f\noch1w\no1ci\no1ck\no2ckar\no3cke\nock2er\no3cki\no2cko\nock3sz\no1cl\no1ç\no1d\no3d2a\nod2dr\no3deb\no3d2e1i\nodein3\node2n1\nodene4\node3sp\no3dex\n2o3dia\no3dir\no3div\no2don\nodo4s\n2odr\no2dre\nodt4\n2o3du\n2o1e2\no2ec\noen1\no4e3s\no2e3t\no3et.\no3ets\no1ë\n2ofa\nof1a2c\nof1am\nof1au\no2f1ei\nof2en\no3fer\nof2f1a\nof2f1in\n1offiz\nof2f5l\nof2f3r\noffs2\nof2fu\n2ofi\nof3l\nof1la\nof4lä\nof4lö\n2ofo\n2o1f1r\nof3ra\nof3rä\nof4rü\nofs1a\nof4sam\nof2spe\nof2spr\nof2s1u\n2oft\nof2tei\nof3th\n2o1g\no2g1ab\noga3d\nog1ala\nog1ang\no2g1ei\noge2l1i\no3gh\nogi2er\nog2lo\no3g4n\nogs2\nog3sp\nog1ste\no1ha\no1hä\no1he\no2h1eis\nohen3s\no2h1ert\no2h1erz\no1hi\nohl1a\nohl3au\noh3lec\nohl1ei\noh3lem\noh3len\noh3lep\noh4lerg\noh4l3erh\noh4lerw\noh3lo\nohls2e\noh2lu\n3ohng\noh2ni\n1ohnm\noh2n1o\no1ho\noho2la\noh1o2p\no2h3ö\nohr1a\noh4rin\noh1ro\noh1s\noh3t\no1hu\noh1w\n2o1hy\n2oi\no1i2d\no3ie\no1im\noimmu4\no1in\noi2r\no2isc\no3isch.\no1ism\noiss2\noi1th\n2o1j\n2o1k\noka2la\nokale4\n3o2kel\noki2o\nok1lä\nok4n\n4okr\nok2s1p\nokt4\n2ol\no1la\no2lab\no2l1ak\nol2ar\nolars2\nol1auf\no1lä\nol4dam\nol4dr\nole3e\nol1eie\nol1eis\noler2\nole3s\nol1ex\no1lé\nol2fa\nol2fl\nolf1r\nol2fra\nol2gl\nol2gr\nol2i\noli3k4\nol2kl\nolk3r\nol2kre\nol2lak\nol2l3au\noll1e2c\nol2l1ei\nol2lel\noll5ends\nol4lerk\noll5erwe\no3lo\nol2of\nolo3p2\nol1ort\nol2str\no1lu\n3oly\n1olym\nol2z1a\nol4z3ern\nol2zin\nol2zw\n2om\no2mab\noma4ner\nom2anw\nom1art\no2m1au\no2meb\nome3c\no2m1ei\no3m2eis\no2mel\no3men.\no2mep\no2meru\nom1erz\nom2es\nomiet1\no2m1ind\nom1ing\no2m1int\nom3ma\nom1org\nom3pf\noms2\nomtu3\no4munt\nomy1\n2ona\nona2b\no2nae\no3nal\non1ap\no2narb\non2au\non3aus\n2onä\nonbe3\n2onc\nonderer5\n2one\none2i\none2n3\nonens2\no2n1erb\no2n1erd\non1erg\non1erö\no3nett\non3f2\non3g2l\nong4r\nong3s\n4o3ni\non2i3d\no4nikr\no4n1im\non3ing\non3k2\nonli4\nonlo2c\non3n2an\non3n2e\nono1\no3nod\no2noke\non1orc\nono3s\nons1a\nonsa4g\non4sam\non2seb\nonse2l\nonsi2\nons3l\nons1p\nonst2h\non3t2a\nont3ant\non4t3end\nont3erw\nont2h\non4t3ri\nont3s\no1nu\n2onuk\non3v\n1ony\non3z\no1ñ\noof2\noo2k3l\no1op\no1or\noor3f\noo4sk\noo2tr\n2o1ö2\no1pa\nopab4\no2p3ad\nop3akt\nopa5s\no1pec\no1pei\no1pe4n\n2opf.\nop2f3a\nop3fah\no2pfe\nop4ferd\nopf5erde\nopf1l\nopf3la\nop1flü\n4oph2\no3phe\no1pi\nopi5a4\nopi3er.\nopi5ers.\nopin2\nop5lag\no2p3le\nop3li\n2o3po\nop4pl\nop2pr\n2o1pr\n1opsi\nop3sz\n1op3t4\no1q\n2or.\nor1a\nor3a2b\no1rad\n2orak\n2oral\no2r3alm\nor4alt\n3oram\nor2and\no2ranh\nor3arb\no1ras\nor3att\no3rä\nor1änd\nor1ät\nor2bar\norb2l\nor1c\n2orca\nor2ce\n4orda\nor2d3am\nor2dau\nor4d3eng\nor2deu\nor2d1ir\nor2dit\n1ordn\nor2do\n2ordr\n2ords\nord3s2t\nor2dum\n2ordw\n4ore\nore4as\no2r1e2ck\no2r1ef\nore2h\nor1eig\no2rein\nor1er\no2rerf\nor1eth\n2orf\nor2fle\norf3s4\nor3ga\n2orget\nor3g2h\n2orgia\norgi1e\nor2gl\nor3gle\nor2gn\n2orh\n2o3ric\no4rient\no3rier\n4oril\n4orin1\n2orit\nork2a\nor2k3ar\nork2s\n2orm\nor4mans\nor4ment\nor5ne.\nor3n2o1\n2o1ro\noro3n2a\n2o1rö\n2orq\n2orr\norr4a\nor3rh\n2ors2\nor3s4a\norsch5li\nor3sh\nor3sz\nor2t1ak\nor4t1an\nor2t1au\nor2tär\nor2tef\nort3eig\nor4t3ent\nor4t3ere\nort3erf\nor2t3ev\nor2the\nort3ins\nor4t3off\nor2tor\nor4tö\nor4trau\nor4t3räu\nort3re\nort3ric\nor2t1um\no3ru\nor2uf\no4r3un\no2r3ü\no2rya\n2o3s2a\nos3ad\nos4an\nosa1s\no3sche\nos4co\n2o3se\nose3e\no2s1ei\nose2n\no4sents\n2osh\no3s2hi\no3sho\n2osi\no3sk\no4ska\nos3ke\no4ski\n2os2kl\n2os2ko\nos2lo\n2oso\n2os1p\no2s3per\no3s2po\nos2sa\noss3and\nos4sä\nos2sei\nos4s3en4k\nos4s3enz\nos2s3o\nos4son\nos2s3p\nos2s3t\nost1a\nosta2b\nos4t3am\nos3tarr\nost4art\nos4ta4s\nos4tei\noster3e\nos6t5er6we\nos2t3h\nos3til\nos3to\nos4t1ob\nost3ran\nost3rä\nost3re\nost3rot\nost3uf\n2osu4\n2o3sy\no3s2ze\no2ß1el\no2ß1en2k\no2ß1enz\no2ß1ere\no2ß1erf\n2o1t\nota2go\no5tark\no2t1au\not3aug\no2teb\no3t2e1i\notei4n\note2l1a\note4lei\not4em3\notemp2\no2t1erw\note2s\n4ot2h\not4he\not5hel\no4t3hi\not3hos\no2thr\no2t1i2m\not2in\notli2\not4ol\not1opf\not2or\noto2ra\noto1s\no3tra\no2t3re\not3rin\not2sa\not3sc\nots1p\not4spa\nots2pe\not2spr\not4terk\not2th\not2t3r\not4tri\no3tü\no2u\noub4\nou2ce\nou1f4l\noug2\nou2ge\nou3gl\no3uh\nou4le.\no3um\no3unds\noun4ge.\n2our\nouri4\nour4ne.\nou3s2i\noutu4\n2ouv\n2o1ü\no1v\nove3s\n2ovi\noviso3\n2ovo\n2o1w\no3wec\nowe2r1\no3wi\no1x\nox2a\nox2e\n1o2xid\nox3l\no2xu\n1oxy\no1y2en\no1yo\noy1s4\n2o1z\noz2e\nozen4ta\no3zi\nozon1\nórd2\nö1b\nöbe2la\nöbe4li\nöb2l\nö2ble\nö2b3r\nöb2s3\n2ö1c\nöch1l\nö2chr\nöch2s\nöchs4tu\nöcht4\nö1d\nödi3\nöd2st\nö1e\n1öf\nöf2fl\nöf3l\nögen2s1\nög3l\nög3r\nö1he\nöh3l2e\nöh3ri\nö1hu\nö3ig.\nö1ke\nö2ko3\nök3r\n3öl.\nöl1a2\nöl1ei\nöl1em\nöl4en\nöl2f1ei\nöl1im\nöl1in\nöl2k3l\nöl3la\nöl2nar\nöl1o2\nöls2\nöl3sa\nöl3sz\nö2l1u\nöl2ung\nölz2w\nöm2s\n2ön\nön2e\nö3ni\nönn2e\nön2s\nön3sc\nön3sp\nö1nu\nöo1\nö1pe\nöpf3l\nöp4s3t\nör3a2\nör1c\nör2dr\nö2r3ec\nö2r1ei\nö2r1e2l\nör2erg\nö2rerl\nö3r2erz\nör2f3l\nör2gl\nö2r1im\nör2kl\nörner2\nör1o2\nörs2e\nör3s2k\nört2e\nör2tr\nöru4\nö2r1une\nö2sa\nö2scha\nö4sch3ei\nö2schl\nö2sch3m\nö2schw\nö2s1ei\nö2sp\nös2s1c\nös2st\nö2st\nös3te\nös2th\nös3tr\nö3su\nö1ß\n2ö1t\nö2t3a\nöte4n3\nöt2h\nöts2\nöt2sc\nöt2tr\nö1v\nö1w\nö1z\nöze3\nözes4\np2a\n1pa.\n1paa\n1pac\npa3da\npa2dr\npa2el\npa1f4r\npag4\npa3gh\npa1ho\n1pak\npa1k4l\npak2to\n1pala\npala3t\n1palä\npa3li\n2palt\npa2nar\npa3nei\npa2neu\npan3k4\n2panl\n3pa2no\npan3sl\npant2\n3panz4\n1pap\npapi2\npapieren8\npapie8r7end\n3para\npa2r3af\npar3akt\n1parc\npa5reg\npa5rek\n2par2er\n2parg\npargel6d\n1park.\npar4kam\npar4kau\npar2kl\npar2kr\n1paro\n2parp\n1partn\n1party\npar3z2\npa3s2p\npa4st\n2paß\n1pat\npat4c\npat4e2\npa5t4r\n1pau\np3auf\npa3uni\n1pä\n3pä2c\npä3cke\n3päd\n3pär\n3päs\npä4t1e2h\npä4t3ent\npä2t3h\npä2to\npät3s4\n2p1b\n2p3c\n2p1d2\npda4\np2e\n1pe.\npe2a\npea4r\npech1\n1ped\npe2en\npef4\npei1\n2peic\npe1im\npekt4s\n2peku\n1pel\npe2l3a4\npel3d\npe2let\npe2lex\npe3li4n\npe4l3ink\npell2a\npell4e\n1pem\npena4\npe3n2al\npen3da\npe4nen\n1penn\npe2n1o\n3pensi\n1pensu\npen3z2\n1pep\npe1ra\nper2an\npere2\n1perl\nper4na\n3pero\npe2rob\nper2r1a\n5pers\nperwa4\npe3sa\npes3s2\npe2st\n3pet\n1pé\n4pf.\np2fab\np2fad\np2faf\npf3ai\np2f1ak\npf1ans\np2fa4r\npf3are\np2f1au\n4p3fe.\np2fei\npf1eim\npf1ein\np3fen.\np2fent\np3fer.\npf2erw\np3f2es\npff4\np2f1in3s\np2f3lä\npf3lei\npf3lie\npf3lo\npf3lu\np2for\npf3r\npf1ra\n2pfs2\npf3sl\npf3sz\n2pf3t\n2p1g\npgra2\n1ph\n4ph.\nph2a\n2phä\n2phb\n4phd\n2p1hei\nphen3d\nphen3s\n2ph1ers\n2phf\n4phg\nphi2ka\n4phk\nph2l\n2phm\n2phn\np3hop\n2phö\nph4r\n2phs\nph3t2\n2phthe\nphu4s\n2p1hü\n2phz\npi2a3\npias4\npi3as.\npi3chl\np4id2\npiegelei8en\npi2el\npiela2\n3pier\n3pik\n1pil\npi3le\npil4zer\npin2e\npingen4\nping3s\n3pinse\npi2o\npi3oi\npi3onu\n3pip\npi2pe\npi3ri\n3pirin\n3pis\n4piso\npi3t2a\npi1th\npit2s\npi2z1in\np1j\n2p1k2\npku2\npkur1\n1p2l4\n2pl.\n3p4la\np5la.\np5lad\nplan3g\n3plä\n2ple.\nple1c\np4leg\np4lem\n3ple5n4\n2plig\np4lik\np4liz\np4lo\n2p3lu\n2p1m2\n2p1n\n1p2o\npo3b4\npo1c\n3pod\n2p3oh\npo2i\npo3id\n3poin\n3pok\n3p4ol\npo2lau\npo3li\npo4lor\n2pond\npo1o2b\npo2p3ak\npo2p3ar\npo1pe\npo2pl\npo3pt\npo1ral\npo1rau\n2porn\npor3s\npor4tin\npor4tre\npor6tri\npos2e\npo4sta\npos4t3ag\npo4stä\npo2s3te\npost3ei\npo2sto\npos6tr\npost3ra\npo3ta\n3pote\npo2t1u\npo2w\npo3x\npö2bl\npö2c\n2p1p\np2p3a2b\npp3anl\nppe4ler\nppe2n1\np2p1f4\np2p1h\np3p2ho\npp3l\npp5lan\npp1lä\np2ple\np2p3ra\np2p3re\np2pri\npp3sa\nppt2\np2r2\n1prak\n1prax\np4rä\n1präd\n1präg\n3präm\n3präs\n2pre.\n2prec\n1pred\npre2e1\n1prei\n3preis\n2p3rer\n3p4res\npri4e\n2prig\n1prinz\n1p4ro\n3prob\n2proc\n3prod\n3prog\n3proj\n2pross\npro1st\n3prot\n1prüf\n2prün\n2p1s\n4ps.\nps4an\np3se\np3s2h\nps1id\np2sö\nps2po\np2st\np3sta\np3stea\np3stel\np3s2ti\npst3r\nps2tu\np3stü\n3p2sy\nps2ze\n2p1t\npt1a\npt2ab\npt3alb\npt3at\np3te\np4t3ec\np4t1ei\npte4l\np4tele\np4t1ent\npt3erei\np4t1erw\np4t1erz\np2th\npt1in1\npto3me\np4tos\npto2w\np2t3r\npt3s2\nptt2\npt1um\npt1urs\nptü4\n3p2ty\npt3z\n1pu\npu1a\npub4\n2puc\npu2dr\n2p1uh\npul2sp\n2pund\npun2s\n2punt\n2pur\npu2s3t\n3put\nput2s\n1püf\n2pül\npün2\n2p1v\n2p1w\npwa4r\n3py1\npys4\npy3t\n2p1z\nqu4\n1queu\n1ra.\nra2ab\n2r3aac\nr3aal\nra3ar\nr1ab\nra2bar\nrab2bl\n2rabd\nr2aber\n2rabf\n2rabg\n1r4abi\nra2br\n2rabs\n2rabt\n2r3abw\n1raby\nra1ce\n2r1acet\nra4cheb\nra4chin\nracht3r\nrach6trä\nra2chu\nr2ack\nr2ad\nr4ad.\nrada2\nra2dam\n2radap\n3radf\n3radl\nr3a2d3r\nrad5t\n1rae\nr2af\nraf3ar\nra2fer\nra3ge\nra3gle\nra2gn\n3r2ahm\n4raht\n2raic\nrail4l\n2r3air\n1rake\n3ra1k4l\nra2kre\nra2kro\n2rakti\n3rakü\nr2al\nr4al.\nra2la2\nral3ab\nrala4g\nr3alar\nral3b\n3r4ald\nra3le\n2ralg\nr4ali\nrali5er.\nrali5ers\nralk2\nral3la\nrall2e\n2rallg\n2r3alm.\nr3alp.\n2ralpe\nr4als\nr3al3t\nr4alt2h\nra2lu\n3raly\nrama3s\nra2mer\n1r2ami\nr2amm\nram4man\nram6m5ers\nram4m3u\nram2p3l\n2r1amt\nramt4s\nr2an.\n4ranc\nr4anda\nr4ande\nran4dep\nran4d3er\nrand3s\n4r3anei\nr4aner\n2ranf\n1rangi\nrani1e\nran2kr\n2ranl\n2r1anm\n2r1anp\n2ranr\nr2ans.\nr2ansp\nran4spa\n2rantr\n2r3anw\nr2ap\n2rapf\nr1ar\nr2ara\n2rarb\n3rarei\nrar3f4\nra2r1in\nr2ark\nr2arp\n2r3arz\nr2as\nr4as.\nras2a\nra4schl\n2r3asph\n2raß\n1rat\nr4at.\nra2t1a\nr3atl\nrat4r\nrat2st\n2r3atta\n4rau.\n3raub.\n4raud\nrau3e2n\n2rauf\n2raug\n3raum\nrau4m3ag\nrau4man\nrau2mi\n3rausc\n2rausg\nrau2sp\n2raus5s\nraut5s\n1raü\nr2ax\n3r2äd\n4räf\n4räg\n2räh\n2räm\n3rän.\n3räni\n3räns\n2r1är\nr2är.\nrä3ra\nrä2s1c\n3rätse\nrä2u\nräu2s\nräu5sche\n4räut\n2r1b\nr2b1ab\nr2b1a2de\nr2bak\nrbal3a\nrba3re\nrb1art\nrb1auf\nrbb2\nrb1ech\nr4belä\nrb1ent\nrbe3r2e\nr3b2la\nrbla2d\nr8blasser\nr4b3last\nr3blä\nr2ble.\nrb3ler\nrb2lin\nrb2lö\nrb2o\nrb4ri\nrb2s\nrb3se\nrb4sei\nrb3ska\nrbs1o\nrb3sp\nrb4stä\nrb3str\nrb2u\nrby4t\n2rc\nr1ce\nr1che.\nr1chen\nr1chi\nrch3l\nrch3m\nrch3r\nrch1s2\nrch3sp\nrchst4r\nrch3ta\nrch6terw\nrch1w\nr1ci\nr2ck1\nr1cl\nr1ç\n2r1d\nr3d2ac\nr2daf\nr2d1ak\nr2d1al\nrd2am\nrdani1\nrd1ant\nrd1anz\nr4dap\nr2dei\nrd2ei.\nr2d1elb\nr3den\nrden3d\nrden4gl\nrde3re\nrder4er\nrderin6s\nr4d3ernt\nrde3sp\nrdi3a2\nrdia4l\nr2d1inn\nrd1it\nrdo2be\nr3don\nrd1os\nr2dö\nrd3rat\nrd4ri\nrdt4\nrd3ta\nrd3th\nrdwa4\n1re\n3re.\nre3aler\nre2alt\nre2am\nre3as\nre3at.\nre3ats\n2reä\nre2b1a\nre2b1l\nreb1r\nreb3ra\nre2bü\nr2ech\nrech3ar\n4rechs\n2reck.\n2recki\n4redd\n2redit\nre1el\nre1er\n3refe\n4reff\n3refl\n3refo\n3reg\n5reg.\nrege4l3ä\nre2hac\nre4h3ent\nre2h1i\nrehl4\nre2h1o\nr2ei.\nrei4bl\nr2eie\n2reig\n3reigew\nrei3l2a\nrei3l2i\nreim2p\nr1ein\nrei3nec\n4reing\nr3eink\n4reinr\nrein8s7tre\nre1in2v\nreister6\n3rek\n4re2ke\nre3la\n2r1elb\nrel2e\nre3lei\n2re2lek\n2r1elf\nre3lo\n2r1elt\nrelu2\nr4em.\nr2emi\n4rempf\n4remu\nr4en.\nr2ena\nrena2b\nre3nal\nre2nä\n3rendi\nren3dr\nre4n3end\nren4gl\n2rengp\nre2ni\nr1ense\n2r1entl\n2r1ents\n2rentw\n4r3entz\nr2enz\nre3or\n3repe\n3repo\n4repp\n3r4er.\n2r1erb\nr2erbr\n2r1erd\nr1erf\nr1erg\nr4ergen\nr1erk\n4r3erken\nr2erki\nr1erl\n4r3erlau\n2rerlö\n2r1erm\nrer2n\n2r1ernä\n4r3erns\n4r3ernt\nr2ero\nre2rob\nr1erö\n3r2ers.\n2r1ersa\nr2erse\n2rersp\nr1ert\nr2erte\n2rertr\n2r1erz\nrer5ze\nr2erzy\n3r4es.\nre2sa\n3rese\n3reso\n2ress\nress2e\nres6s5erw\n3rest\nre1sta\nre2s2tu\n3resu\nre2thy\nre2u\nreu3g2\n2reul\nre3uni\n2r1eur\n2reü\n2r3evid\nr1ew\nrewa4r\nre2wi\n2r3e2x1\n3rez\n4rezi\n1ré\n2r1f\nrf1ält\nrf2äu\nr2fent\nrf2es\nrfi4le.\nrf3lic\nrf3lin\nrf4lö\nr3flü\nrfolg4s\nr3for\nrf4ru\nrf4rü\nrf2sa\nrf2s1ä\nrf4s1id\nrf2s3pr\nrf2s3t\nrf2ta\nrf3t4r\nrf2u\n4r1g\nrg2ab\nr2g1a2d\nr2g1ah\nr2g1ak\nrg2an\nrge4an\nrge2bl\nrge4l3er\nrgen4z3w\nrge4ral\nrge4tap\nr2geto\nrgi4sel\nr2glan\nr2gleu\nr2glig\nr2gno\nr2g1ob\nr2g3ral\nr2greg\nr2gres\nr2gret\nrg3rin\nrg3s2p\nrgs4tr\nrg5s2tu\nr1h4\n2rh.\n2rha\nr2ha.\n2rhä\n3r4he.\n3r4hen\nr3her\nr2hoe\nrho2i3\n2rhol\n2rhö\n2rhs\nrhu2s\n1ri\nri3am\nria1s\nri3at\nrib2bl\nri1ce\nri1cha\nrid2\nri2d3an\n2ridol\nr2ie\nrie2fr\nri1el\nriene4\nrien3s\nrie2nu\nri1er.\nri4ere\nri3ers.\nri3esti\nri1eu\nri2f1a\nri2f1ei\nri2fer\nri2f1o\nri2fr\nrif3s\nrif4ter\n3rig\n5rig.\n5rige\nri4gene\n5rigj\nrig1l\n4rigr\nrik1l\nri4kla\nr2imb\n2rimp\nrim2s\nrim4sc\nr2i3na\n2r1ind\nrin4dex\nrin4diz\nri3n4e\nrine1i\n2r1inf\nrin2fo\nring3l\nrin2gr\n2r1inh\n2rinit\n2rink\n3rinn\n6r5innenm\n4r3inner\n4rinnta\nr1innu\n2r1ins\n3r4ins.\nrin4so\nrin2sp\nr4inspi\n2rint\nrin4teg\nrin4t5r\nr1inv\nr2inva\n2rinve\n4r1ir\nr2is\nris2a\nri4scho\nri4schw\n3risik\nri3so\nri4s1p\n3riss\nri2st\nris6t5ers\nr2it\nr3i2tal\nri3t2i\nrit4r\nrit2tr\n5ritu\nrix1\n1rí\n2r1j\n2r1k\nrk2am\nr2käh\nr3klau\nr2klis\nrk4lo\nrk2lu\nrk4n\nr2k5nu\nrk3räu\nr2k3rea\nr3kri\nrk3rin\nrk2s1e\nrk3shi\nrk2sp\nrk1st\nrkstati6\nrk4stec\nrk2ta\nrk4t3eng\nrk4t3erf\nrkt3ers\nrk6tersc\nrk4t3erw\nrk2tin\nrk2t1o2\nrk2t3r\nrk3tra\nrk4tri\nrk1uh\nrk2um\nrku2n\nrk1uni\n4r1l\nr3l2a\nrl2e\nrle2a\nr3lec\nrle2i\nr3let\nr3l2i\nrli2s\nr3l2o\nrl2ö\nrlös3s\nrl2s1p\nrl3ste\nrl2s3to\nrl3t\n4r1m\nr3m2ag\nrma2la\nr2m1ald\nrm1ans\nrm1anz\nrm1a2p\nr2maph\nrm2är\nrm3d2\nr3me.\nr2m1ef\nr2meo\nrm2es\nr2mide\nr2m1im\nr2m1o2ri\nrmo1s\nrm3sa\nrm3sta\nrmt2a\nrm2u\nrm3ums\n4rn\nrna2b\nrna4n\nrn2and\nrn3ani\nr2n1anz\nrn2a2r\nrn3are\nrn3ari\nr2nau\nrnd4\nrn3dr\nr3ne\nrn3e4ben\nr4nef\nrn2ei\nrn3eif\nr4n3eis\nrne2n\nr4n1ene\nrn3ense\nr4nerf\nr4n1erg\nrn4erhi\nr4nerk\nr4n1ert\nr5nes\nrn2et\nr4nex\nrn3f\nrng2\nr3ni\nr4n1in\nr3nod\nr2n1op\nr2n1or\nrn1ö\nr1nöt\nrn3s2ä\nrn3s2p\nrn3s2z\nrn3t2e\nr1nu\nrn1ur\nr1nü\nr1ny\nro2bei\n2robj\n3robo\n2robs\nro1c\n3rock.\nr2o3de\nro3e4\n2rof\nroh1l\nroh3na\n3r2ohr\n3roi\nro1ir\nro3le\nrol4lan\nrol3l4en\nrol3s\n2roly\n4rom.\nro2mad\nro2mer\n4romm\n4romt\nr2on\n3ronn\nrons2\nron4tan\nro1ny\nro1pe\nro3ph\nr1or\nr2ora\nror3al\nro2rat\nro2rei\nro2r1o\nror3th\nro3sh\nro3s2i\nro3smo\nros2p\nros2s1c\nro3st2a\nrost1r\n4roß\nro2ßu\nro4tag\nrote3i\nro2tho\nro4tri\nrots2o\nrot2ta\nro3t2u\nro3unt\n3rout\n2rox\nrö2b3l\nrö2du\n2röf\n4rög\n1r2öh\nr1ök\n1r2öl\n3römi\n4röp\nr1ör\nr2ös.\nr2öse\n2r1p2\nrp4a\nrp4e\nrpe2re\nrpe4r3in\nrpf4\nr2pli\nr3po\nrpro1\nrps3t\nrp3t\nr3pu\nr1q\n2r1r\nrr2ab\nrr2ar\nrr1äm\nrrb2\nrr1c\nr3r2e\nrre4ale\nrrer4s\nrre2st\nr4rew\nrr2he\nrrik2\nrr2n3a\nrr2o\nr2r3ob\nrro3m\nrr2st\nrr3stu\nrr2th\nr3ru\nr3r2ü\nrrü1b\n4r1s\nrs3ab\nr2s1a2d\nr4samp\nr4s1amt\nrs2an\nr2s3ang\nrs3anp\nrs3ant\nrs3ar\nr3sch2e\nr6scherl\nrsch2l\nr3schu\nr3schw\nr2sein\nrse2n1\nrs2end\nrse4ne\nrs1ere\nrs1erö\nrs1ers\nrs1erz\nrs1eta\nr3sho\nr3si\nrs2kal\nrs2kan\nrs2kie\nrs2kis\nrs2kl\nr4sko\nr4skr\nr4sku\nrs3l\nrs4no\nr3so\nr4sob\nr4s1op\nr4sord\nr4s3ort.\nrs2p4\nr2s3ph\nrs3s2\nr4stant\nrs2tec\nr6st5eing\nrs4temp\nrs4terb\nrs4t3er4w\nrs2th\nrs2ti\nr3stie\nr2stin\nrst3ing\nr2stip\nr3sto\nrs4tob\nr4stot\nr3stö\nr3s4tr\nrst3ran\nr6strang\nrs2tu\nr3s4tü\nr3swi\nr3sy\n4r1t\nrtal2\nr2t1alm\nrtals1\nrt1am\nrt1ang\nrt1ann\nrt1ant\nrt1anz\nr2t1ar\nrt3a4re\nr2t3att\nrt1är\nrte1e2\nrt4eif\nrtei3la\nrtei1s4\nr2telf\nr2temo\nrte2n1\nrten3s2\nrt3erei\nr4terfo\nr4t3erh\nr2t1erk\nr4t3er4la\nr4t3erle\nr4t3ernä\nrter4re\nrt1ers\nrte3s2k\nr2thi\nrt3hol\nrt2hum\nr2t1id\nr2t1ima\nr2tinf\nrto1p\nrt1or\nrto2ri\nr3tö\nr4trak\nrt3rec\nr4treis\nr5tri\nrt1ros\nrtrü2c\nr4ts\nrt4s1eh\nrt2so\nrt2spa\nrt2spr\nrtt4\nr2t1urt\nr3tü\nrt3z\n1ru\nru1a\nru3a2r3\nrube2\nruch3st\nru6ckerl\nru2cku\nrude2a\nru2dr\n3ruf\nru2fa\nruf2s3\n4rug\n2r1uhr\n3ruin\nru1ins\nru1is\n2rum\n4rumf\nru2mi\n4ruml\nr2ums.\n4rumz\n2r1una\n2rund\nrun2d1a\nr2unde\nrund3er\nrun6derl\nrun6ders\nrun6derw\n2r1unf\n2rungl\n2r1u2ni\n4r3unio\nrun2kr\n2r1unl\n2r1unm\n4runn\n4r3unt\n2runw\nru3pr\n4r3ur\nru2ra\nru2r1e\n5ruro\nru2si\nrus2s1p\nrus4st\nru2st\nru3sta\n3rut\nrut3h\nru2t1o2\nru2t3r\n4ruz\nru2zw\n1rü\n2rüb\nrü1ben\nrü1ch\n4rümm\n2r1v\nrve4n1e\n2r1w\nrwun3s\n4r1x\n1ry\nry2c\n2r1z\nrz1a2c\nrz2an\nr2zar\nr2zas\nr5zene\nrz1eng\nr4z3ents\nr2z1erf\nr2z1erg\nr2z1erk\nr2z1erw\nrz1id\nr3z2of\nrz1op\nrz2ö\nrz3te\nrz2th\nrz2t3ro\nrzug2u\nr3zwä\nr3z2wec\n1sa\n3sa.\n3saa\n2s1ab\nsa2be\n3sabet\nsa2bl\nsa3ble\nsa2br\n4sabs\nsa2cho2\nsach3t\n2s1ada\ns1adm\n2s1a2dr\n3safa\nsa2fe\n2s3aff\n3safi\nsa1f4r\n3saga\nsa4gent\nsag4n\nsa2gr\n3sai\nsa3i2k1\nsail4\n2s1ak\nsa2ka\n3saki\n3sakr\n4sakt\n3s2al.\nsa4l3erb\nsa2l1id\n3salo\nsal2se\n2s1alt\n3s2alz\n3sam\ns3ameri\n5samm\n6s1amma\n4s1amn\ns1amp\nsam2to\ns1an\ns2an.\n2s3a2na\ns3anb\ns2an2c\ns2and\ns4and.\nsan4dri\n3sang.\n2s3anh\n3sani\n2s3anl\n2s3ans\nsan4sk\n4s3antr\n2s3anw\n2s1ap\nsa2po\n3sapr\n2s1ar\n3s4ar.\n3s2ara\n4s3arb\n3s2ard\n3sari\ns3arr\n3s2ars\n4sarti\ns1asp\n4s3a2sy\n3sat\nsat2a\n4s3ath\n4s3atl\n4satm\nsa2tr\nsa3ts\nsat4z3en\ns1a4u\n3sau.\n3sauc\n3saue\n2s3aufb\nsau2gr\n3saum\n3saur\nsauri1\n2s3ausb\n3s2ause\ns3ausw\n2s3av\nsa2vo\n1sä\ns3ähn\n3säl\ns1ält\n2s1äm\n2s1änd\n3sänge\n2s1är\n3s2ät\n3säul\n2säuß\n4s3b4\nsba4n\nsbe3r2e\n1sc\n2sc.\n2scam\ns2can\ns2cap\n2scar\n2s1ce\n6sch.\n2schak\ns4ch2al\n4schanc\n4schang\n2schao\ns4chä\n4schb\n4schc\n2schd\n3sche.\n2schef\nsch3ei.\n4schemp\nsch2en\n3sches\n4schess\n4schex\n2schf\n2schg\n2schh\nschi4e\ns4chim\n4schiru\n3schis\n2schk\ns4chl\n4schle.\n6schlein\nsch6lit\n2schmö\n2schn.\n2schox\ns4chö\n2schp\n2schq\n4schre.\n4schrin\nsch3rom\n4schrou\n6schs\nschs2e\nsch3s2k\n4sch3t\nscht2a\nscht4r\ns4chu\n4schunt\nsch2up\n3schü\n2schv\n4schwet\nsch4wil\n2schz\n2scj\n4s3cl\n2sco\n3s4cop\n3sco4r\ns2cr\n2scs\n2scu\n4s3d2\nsda3me\nsde1s\nsdien4e\nsd4r\n1se\nse3at.\n2s1e2ben\nseb4r\n2s1echo\ns1echt\n2s1e2ck\n3see\nse1ec\nse2e1i4\nsee3ig\nseein2\nse1er.\nse1erö\n2s1eff\nse2gal\nse2gl\nseg4r\n3seh\nse2h1a4\nse3he\nse4h1ei\nse4hel\nse4herk\nse2hin\nseh1l\nseh3re\nseh1s\nseh3t\nse2hüb\n2s1ei.\n2s1eig\ns1ein\n5s4ein.\n2seinb\nsein4du\nsei3n2e\nsein4fo\n4seing\n2seinh\n4seink\n2seinl\n2seinn\n4seinr\ns4eins.\n4seinsp\n4seinst\n2seinw\n4s1eis\n3s2eit\n3s2ek\ns2el.\nse2l1a\nse3lad\nsela4g\nse3lam\nsel1ec\n4selem\nse4l3erl\nsel3ers\n2self.\ns3elix\nse2l3ö\ns2els\nsel3sz\nsel3tr\ns4e3ma\n2s1emp\n3s2en.\nse4nag\nse2nä\n2s1endl\nsen3gl\n3s2eni\n3senk\nse2no\nse4nob\n3s2ens\ns2ent.\n4s1entf\n2s3entg\ns2enti\n2s1ents\n2sentw\n2sentz\nse2n3u\nseo2r\n4s1e2pos\n3seq\ns4er.\n3sera\nser3a2d\nse2r3al\nse5ref\ns3ereig\n6sereign\nse4r3eim\nse4r3enk\nser2er\n2s1erfo\ns2erfr\ns3erfü\n4serfül\nser3g\ns2ergr\ns1erh\n2serhö\n3seri\n4serken\n2s3ernt\nse2rob\n4s3eröf\ns2ers.\n2sersa\n4serseh\ns4ert.\ns2erta\nseru2\nse4r1uf\nse3rum\nse3rund\n3s4erv\n5ses.\nse2sel\nse1sta\nse3su\n3set\n4se4tap\nse2tat\n4s1e2th\nse1u2n\n2s1ex\nse2xe\n4sexp\nsex3t\n1sé\n4s3f4\nsfal6l5er\nsflo4\n4s3g2\n2s1h\nsh2a\n3s2ha.\nsha2k\n4s3han\n1shas\ns3hä\ns3h2e\n3shi.\n3shid\nshi4r\nsh3n\ns3hoc\n4shof\n3shop\nsho4re\n3show\ns3hö\nsh4r\n1si\nsi2ach\nsi3ach.\nsi2ad\nsi3am.\n2siat\nsib4\n5si1c\n2s1i2deo\ns2ido\n3s4ie\nsiege4s\nsien3\nsi3ene\nsi1err\nsie2s\nsi1f4\n3s4ig\nsi2g1a2\nsig4n\nsi3gnu\nsi2g3r\nsig4st\nsi2k1ab\nsi2k1ä\nsik3erl\nsi2ki\nsi4k1l\nsi2kr\nsik3s\nsik3t4\nsi2ku\n3silo\n2s1imm\nsi3n4a\n2s1ind\n2s1inf\nsing1a\nsin3gh\nsin3g4l\nsin2gr\nsing3sa\n4s1inh\nsin1i\nsini1e\n2s1inq\n2s1ins\n2s1int\n4s1inv\n3sio\nsion4\n3s2is\nsi2sa\nsi4schu\nsi2s1e\nsi2s1o\nsi2s1p\nsis3s\n3s2it\nsi2tau\nsit3r\nsi2tra\nsi3tu\nsiv1a\nsive3\nsi2vr\n1sí\n2s1j\n2s1k2\n4sk.\n3skala\n4skam\n4skanz\ns3kar\n4skas\nska4te.\n4skateg\nska4tes\n4skb\ns4kep\n3s2ki.\ns2kif\ns2kig\n3s2kik\n4skir\nski1s\n3skiz\nsk4l\n4s3klas\nsk4n\n4skom\n4skor\n4skow\n4skö\n4sks\n4sk3t\n3skulp\n2s1l2\n3slal\n4slan\ns2law\ns3lä\nsl3b\ns3le\nsler3s\ns3li\n3s4lip\nsli4tu\ns3lo.\nslo3be\ns3loe\n2s3m2\n2s3n4\n4sna\nsnab4\nsni3er.\nsni3ers\n4s5not\n4snö\n1so\n3so.\nso4a\n2s1o2b\nso1c\nso3et\n3soft\n3sog\ns1o2he\n4sohng\n2s1ohr\n3sol\nso3la\nso4l1ei\nsol4ler\n4so2ly\n3som\n3s2on\nson3au\nsone2\nson5ende\nson3sä\nso2ny\nso3o\n2sopf\n3sor.\nso1ral\ns1orc\n2s3ord\nso2rei\n2s1orga\n5s2orge\n2s1o2rie\nso2r1o2\n3sors\nso4ru\n3sos\ns4os.\n4s1ost\nso3unt\n3sov\n4s1o2ve\n3sow\n2s1ox\n3soz\n1sö\nsö2c\nsö2f\n2s1ök\n2s1ö2l\ns1ös\n1sp2\n2sp.\n2spaa\n2spak\ns2pan\nspani7er.\n2spap\n2spara\n2sparo\n3sparu\n3spaß\n2spau\ns2paz\ns2pä\n3späh\n2spär\ns2pee\n2spel\n4spensi\nspe3p4\n2s1peri\n2sperl\ns2perr\n2spers\n2spet\n3s2pez\n4s3pf\n2spha\ns3phe\n2sphi\n3s2pi4e\n4spier4\nspi2k\n4spil\n3spio\n4spip\n4spis\n2spl\n4spla\n4splä\n3s2pli\ns3p4lu\ns3pn\n2spod\n2spog\ns2poi\n2spok\n4spol\n4s3pos\ns2pott\n4spr.\ns2prac\ns2pran\n2sprax\n2spräm\n4spräs\n3s4prec\n2spred\n2spres\n2sprob\n3spross\n3spru\n2sprüf\n2s3ps\n2spt\n2spun\n2spup\n3spur\n4sput\n4spy\n2s1q\n4s3r4\nsrat2s\nsrat4sc\nsret3\nsrö2s1\nsrücker6\nsrü2d\n6s1s\nssa3bo\nss1a2ck\ns5saf\ns3sag\nss1aj\ns3sal\ns4s1ala\ns4s1alb\ns4s3amt\ns4s3ang\ns2sano\ns4sans\nss2ant\ns4s3anz\ns3sa1s2\nss3att\ns3s2ä\ns4sce\ns4sco\nss1ec\ns2s1ega\nsse3ha\nsse3inf\nsse3in4t\nsse6r5att\nss1erö\nss3erse\ns3s2es\nsse3ta\nss3l\nss1off\nssoi4\ns2s1op\nss1ori\nss2po\ns2spro\nssquet4\nss3s4\nsst2a\ns3stel\nss2th\nss2ti\nss4tip\nss2tur\ns3stü\nss1ums\ns1t\n6st.\ns2ta\n4sta.\n3staa\n2stabb\nst2ac\n3s4tad\n3staff\n2stag\n3stah\n2stak\n2stale\ns3ta3li\n2stalk\nst1alp\nst1ami\n4stan.\nsta4na\n3stand\n2stani\n4s3tann\n2stans\n2stanw\ns4tar.\n4stari\ns4t2ars\ns3tat.\ns4tau.\n2stauf\n2staum\n3staur\n2staus\n3staus.\n2stax\n3s2tä\n4stäg\n4stält\ns4tänd\n5stätt\ns3täus\n2stb\n2st3c\n2std\nst2e\n4s5te.\n2stea\n4stechn\ns2ted\n4stee\n3s2teg\nste2gr\n3s4teh\ns2te2i\n3steig\n4steil\n3steilh\nstei4na\n1s2tel\n2stel.\nstel4l3ä\n2steln\n2stels\n2stem\n4stem.\nste4mar\n4sten\ns5ten.\ns4t3ends\ns4t3engl\nst4ens\ns4t3entf\ns2tep\n2ster\n6s5ter.\nste6rers\n4sterm\n3sternc\n4stes\ns4t3ese\nstes6se.\nste4st\n2stet\ns4teti\n3s4tett\n3s2teu\n1steue\n4steuf\nst3ev\n4stex\n2stf\n2stg\n4sth\ns4thä\ns4thi\ns2t3ho\ns2thu\n2stia\n2stib\ns2tic\nsti2e\n2stie.\ns2tieg\ns2tiel\n2stien\n3s2tif\n2stig\n2stik\ns2til\n3s4tim\ns4tinf\ns3tinn\nst1ins\n2stio\n1s2ti2r\n2stis\nst1i4so\n1stitu\n2stiv\n2stj\n2stk\n4stl\n4stm\n2stn\ns2to\n2sto.\ns3tob\n2sto3d\n4stod.\n1stof\ns4toff\ns4t3om\n4ston\n4stoo\n2stopo\n2stor.\n2store\n2storg\n2stori\ns3tort\n2stose\nsto3s2t\n1stoß\n4stote\n4stou\n2stow\n2stoz\n1stö\n2stöch\n2stöt\n2stp\n2stq\ns2tr\n2strad\n2strag\n1s4trah\n4strai\n4strak\n2stral\n4strans\n3s4tras\n5straß\n4straum\n4s5träg\n4sträne\n4s5tref\n4streib\n5st4reif\nst3renn\n2s4trig\n1s4tri2k\n2s5tris\nst3roll\nstro4ma\n1stru\n2strua\n2strug\n3struk\n4st3run\n2strup\n2s4t3s4\n2st3t4\nst2u\n1stub\n4stuc\n3s4tud\n2stue\n3stuf\n3stuh\n2stum2s\nstum4sc\n2stumt\nstu2n\n2stun.\n3s4tund\ns2t3uni\n4stunn\n2s3tuns\n2stunt\nstu3re\nst3url\n2sturn\n2st3urt\n2s3tus\n1stü\n2stüch\n2stür.\n2stüre\n2stürg\n2stürs\n3stüt\n2stv\n2stw\n3s2tyl\n4st3z\n1su\nsu1an\n3su2b3\nsu4ba2\n4subi\n3su1c\nsu2cha\nsuch4st\n2s1u2f\n2s1uh\nsu1is\nsu1it.\nsul2a\nsul2i\nsult2\nsu2mar\nsu2mau\n3s2ume\nsu2mel\nsu6m5ents\ns3umfe\n3summ\nsum1o2\nsu2mor\ns3umsa\ns3umst\nsu2n\n3sun.\nsun6derh\nsu4ne\ns1unf\n2s1uni\n4sunt\n3s2up\nsup3p\nsu2ra\n2s1url\ns1urt\ns4u2s1\nsu3sa\nsu3sh\nsu3si\nsus3s\n3suv\n1sü\n2sü4b\n3süc\nsü2d1\nsüden2\n3sün\n4s3v\n2s1w\ns3wa\ns3we\nsweh2\n4swie\n4swil\n1s4y\nsyl1\nsy4n3\n2s1z\n4s3za\n4s3zei\ns2zena\n3s4zene\n4szent\ns2zes\n4s3zet\ns2zis\nsz2o\n4s3zu\ns3zü\n4s3zw\n2ß1a2\n2ß1b2\n2ß1c\n2ß1d\n1ße\n2ß1ec\n2ß1e2g\n2ß1ei\nße2l1a\nßen3g\nße2ni\nße2no\nße2ro\nß2ers.\n2ßerse\nßer3t\n2ß1f\n2ß3g2\nßge2bl\n2ß1h\n1ßi\nßi2g1a2\nßig4s\n2ß1in\nß1j\n2ß1k4\n2ß1l\nßler3\n2ß1m\n2ß1n2\nß1o2\nßos2\n2ß1p2\n2ß3r2\n2ß1s2\nßst2\n2ß1t\n1ßu\n2ß1um\n2ß1ü\n2ß1v\n2ß1w\n2ß1z\n1ta\n3ta.\n4taa\n5taan\n4tab.\nta2b1an\n2t1abb\n3tabel\n2taben\nta4bend\n2tabf\n2tabg\n2tabh\n2tabk\n3table\n2t3abn\nta2br\n4tabs\n2t3abt\nta2bü\n2tabw\n2tabz\n2t1ac\n3tacu\nt1ada\ntadi3\n2t1a2dr\nta3d2s\n3taf.\n3taf2e\n4taff\nt1afg\nt1af4r\n3t2ag\nta2ga2\nta2g1ei\n4t3a4gent\n4ta3gl\nt3ago\ntag4st\ntah2\ntah3le\ntahl3sk\nta3i2k\ntai2l\nta1ins\ntai4r\nta1ir.\nt1a2ka\nta2kro\ntak6ta\n3taktb\n3t2aktu\n2takz\n3t2al.\nta2la\nta3lag\nta3lak\ntal3d\n3t4ale\nta4lens\ntal2lö\n3talo\nta2l1op\n2talt\n2tam\n3tame\nta2mer\nta2mi\nt1ampl\nt1amt\n3tan.\nt1a2na\n2tanb\n4t2and\nta3ne\n4tanf\n2tang\n3tani\nt2ank\nt3ankl\n4tanl\n2t1anme\n4t1anna\nt2ano\nt1ans\n3t2ans.\n4t3ansi\n4t3ansp\nta2nu\n2tanwa\n2tanwä\nt2anz.\nt1anza\ntan6zerh\nt1anzu\nta3or\nta2pe.\nta2pes\n2tapf\nta2pl\n2tarb\nta4rens\nta4r3ere\n3t4a3ri\n4tark\n2t1arm\n2tart\nt1arti\ntar2to\nta2ru\n2t1arz\n3tas\nta3sa\n4t1asp\nta2ta2b\nta2tan\nta2tau\ntat3ei\nta2tem\nta2t3er\nta2th\ntat3he\nt3atl\nt4atm\nta2tom\n4tatue\nta2t1um\n2t1auf\n4taufg\ntau3f4li\n4taufn\nt1auk\n3taum\nt1ausb\n3tausc\ntau6schr\ntau6schw\nt2ause\n4t3ausg\nt1ausk\n4tausl\n4t3auss\n4t1ausw\n3tav\n3tax\ntaxi1s\n1tä\n4täb\ntä1c\n4täd\n3täe\n3täg\n4tägy\n2täh\n2t1ält\n4täm\nt1ämt\nt1ängs\n3tänz\nt1äp\nt2är.\ntä2ru\ntä2s\nt2ät\n4tätt\n2täuß\n2täx\n1tà\n4t3b2\ntbe3r2e\ntblock5e\ntblocken8\n4t1c\nt3cha\nt3che\ntch2i\ntch3l\nt2chu\ntch1w\nt4ck\nt3cl\nt3cr\n4t3d4\ntdun2\n1te\n3te.\nte2a2\n2teak\nte3al\nte3an\n3teba\n3t4ebb\n4t1e2ben\nt2ech\nte3cha\n3techn\n2teck\nteck2e\nte2cki\nte1em\nte2en3\nte1erw\nte2es\n2teff\nteg3re\n2teh\n3teha\n3tehä\n3t2ei.\nt2eie\nt3eifr\nteik4\n3teil\n4teilhe\n2t1ein\ntein3e4c\nt3einge\nt3einla\n4teinn\nt3eis.\nt3eisb\n5tel.\n3tela\nte2l3ab\nte2l1ac\nte2l1au\ntelb4\n3te3le\ntel1eb\ntele4be\nte4l1ec\nte4l1eh\nte4lein\n2telem\ntel1en\nte4lerd\nte4leu\n4t3elf.\n3telg\nte2l1in\nte2lit\n3telk\ntell2e\n5teln\nte4lost\nte2l1ö\n3telp\n5tels\ntel3s2k\n3telt4\ntel3ta\ntel3th\n3tem.\nte2m1ei\nte2min\n2temme\nte2m1o2r\n3temper\n2tempf\ntem3s\nte4m1u\n3ten\nt6en.\nten3a\ntena2b\nte4na2d\nte4na4g\nte4nas\nte4nau\nte2nä\nt4enb\nten3da\n4t3endf\nt6endi\n4t1endl\nt6endo\n4t3endp\nten3d4r\nte2n1e2b\nte2nef\nte3n4ei.\nten3eid\nten3ens\n4tenerg\nte2net\nten3g\n4t1eng.\nten4gla\nt4enh\nte2ni\nte4n3in\nt4enj\nt4enm\nten3n\ntens2e\n4tensem\nt4enta\nt3entb\n4tentd\nt4ente\n4tentn\ntent3ri\n4t3entw\n4t3entz\nten6zerh\nten3zw\nt3e2pi\n3t4er.\ntera2b\nte1raf\nter3am\nte3ran.\n4terbs\n4terbt\n3terc\n4t3erde.\nte2re2b\nte4r3eif\nte2rel\nter3end\nte4reng\nte4rerk\nterer4z\n4terfol\nt4erfr\n4terfül\n3ter3g2\n6tergrei\nt6ergru\nt4eri\nte3ria\n4terklä\n2t1erlö\nter4mer\n3termi\nter4n3ar\n2ternc\nt3erneu\nt4ero\nt3erö\nter4re.\nt4ers.\nt6erscha\nter4ser\nterst4\nt4erst.\nt4ersti\nt4erstu\ntert2\nteru2\nte4r1uf\nter4wäh\n6terwerb\nter3za\n2t3erzb\n3tes\ntesa2c\nte2san\ntesä2c\nte2sel\nte2spr\ntes3s2\nt2est\ntes3tan\ntest3ei\ntes6ter6g\ntes6terk\ntestes4\nte2su\n3tet2\nt2et.\nte2tat\n4teth\n4tetl\nteu3ere\nteu3eri\n3teuf\n3teum\nte1un\n3teur.\nteu2r3a\nte2vi\nte1xa\n2t3e2xe\n2t1e2xi\n4texp\n3text\n2t1exz\n4t1f4\ntfi2l\n4t1g2\ntger2\nt1h\n4th.\n2th4a\n3t4ha.\nt2hag\nt3hai\nt2hak\n3thal.\n4t3hau\n2t3hä\nth2e\n1t2he.\n3thea\n2theb\nt2hec\n2t3hei\nt4hein\nt2hek\nt2hem\n1then\nt4hene\nt4heni\n3theo\n2therr\nt2hes\n3these\nt2heu\n1thi\nt2hik\n2t3hil\n2t3him\n2thk\n4th3l\n4th3m\n2th3n\n1t2ho\n2t3hoc\nt3hof\n2t3hoh\nt4hol.\nt3hor\n2t3hot\nthou2\n4t3hö\n2thp\n1th2r2\n4thrin.\n4thrins\n2ths\n2thub\n4thun\n2thü\n2thv\nt2hy\n1ti\nti2ad\nti3a2m\n3tib4\nti1ce\ntiden2\nti4dend\nti2deo\n3tief.\ntieg4\n2tieh\nti1el\nti3e4n3\n3ti2er\ntie4rec\nti1et\nti1eu\n3tif.\nti1fr\n4tift\n3tig\nti4gerz\n3tik\nti2kam\nti2kar\nti3k2er\nti2kin\nti2krä\nti2lar\nti2lau\nti2lei\nti2lel\n3tilg\nti2lö\ntil3s\ntilt4\nti2lu\nti2ma2g\nt2imi\ntim2m1a\n4t1imp\n3t2in.\nti3na\nt1inb\n4t1ind\nti3n2e\nt1inf\ntin2g1a\nting3l\nting3s\nt1in1it\n2t1inj\ntin2k1l\n3t2ins.\n4t1inse\n2t1int\nti1nu\n4t1inv\n3tio\nti3or\n3tip\nti3pl\nti4que.\nti1rh\n3tis\nti4scha\ntisch3w\nti2sei\nti2sp\nti1sta\n3ti3t2e\n2ti3tu\ntium2\n3tiv\nti2van\ntive3\nti2vel\nti4v3erl\nti2v1o\nti2v3r\nti2za\n2t1j\n4t3k4\n4t3l\ntl4e\ntle2r3a\n6t5li\ntlung4\n4t3m2\ntmal2\ntmen6t3\ntmo4des\n4t5n4\ntnes2\ntnes4s\n1to\n3to.\nto4as\nto5at\n4tobj\ntob2l\nto1c\n3tocht\nto6ckent\n3tod\ntode2\n4to2d1er\nto4d1u\ntoi4r\n3tok\nto3la\n3tole\n4tolz\ntom1e2\nto2men\n2tomg\n3ton\nto2nau\n3too\nto2pak\nto2pat\n3topo\n2topt\n3tor.\nto1ra\nto2rau\nto4rän\n4torc\nt1ord\n3tore\nto2rel\nto3ren\nt1org\nt3orga\n3torin\ntor3int\nto2rö\n3tors\nt1ort.\nto2ru\nt2orw\nto3sc\n3tose\nto4sk\ntos2p\n4toss\n3tost4\nto1sta\n4toß\n3to3te\nto2tho\n3totr\ntots2\n3t4ou\ntouil4\nto3un\n3tow\n2tö\n3töch\n4töf\n4t1ök\ntö4l\n3tön\nt1öst\n4töß\n3töt\n4t3p2\ntpf4\n2t1q\n1t2r4\n2tr.\n5tra.\n3trac\ntra3cha\n4tract\nt3rad.\ntra4dem\ntra4far\n3trag\n6trahm\n5t4rai\n3trak\n3tral\n3t4ran.\n2trand\n3trank\nt3rann\n3trans\nt3rase\nt3rasi\n4traß\n5träc\n3träg\n3träne\n4träs\n4träß\n4t5re.\ntre4ale\n4treb\ntre2br\n4trec\nt3rech\nt4reck\n6t3red\n5t4ree\n3tref\n4trefe\n4trefo\n4treg\nt4rei.\n3t4reib\n4treic\n2treif\nt3reig\n2t3reih\nt3rein\nt3reis\n6treit\nt3reiz\n2trek\n6t3rel\nt4rem\nt4ren.\n3trend\n4trendi\nt3rent\n2trepe\n2trepo\nt4repr\nt4rer\nt4res.\nt4ret\ntre2t3r\nt4reu\n3treuh\nt3rev\n2trez\n5t4ré\n2t3rh\n3tri\n4tric\n2trid\n5trieb\ntri4er\n5trigg\nt3rind\n4tring\ntri3ni\n4trinn\nt4rip\n4tript\ntri2x\ntrizi1\n3tro.\n3troe\n3t4roi\ntro2ke\n4trom.\ntro2mi\n4troml\n3tron\n2t3roo\nt4rop\n3tropf\n3troy\nt3röc\n2tröh\n3trös\n2t3röt\n3trua\n4truk\ntrum2\ntrums1\n2trund\n3t4runk\n5t4rup\ntru2th\nt4rüb\ntrü1be\ntrü1bu\n2t3rüc\ntrücker6\nt4rüg\ntry1\n2ts\n4ts.\nt4sa4b\nt3s2ac\nts1ad\nt2s1ah\nts1al\nt4s1amt4\nt2san\nt4s3ar\nts1as\nt2sau\nt2s1än\nt3s2cha\nt4schar\nt3sch2e\nt4schef\nts4chem\ntsch4li\nt4schro\nts4cor\nt2s1e2b\nt3seil\nt4seind\nts1em\ntse2n1\nt2s1eng\nt2s1ent\nt2s1er\nt4s3esse\nt2s1i2d\nts1ini\nt2s1ir\nts3kr\nt1slal\nts1o\ntso2r\nt3sou\nt2sö\nt3spal\nts1par\nts4pare\nt2spä\nts2ped\nt3spek\nt2sph\nt3s2pi\nt2spo\nt3s2pon\nt3s2por\nt4sprei\nts3s4\nt1st4\nt4stag\nts3tak\nts4tal\nts3täti\nt2s3tep\nt3s4tero\nt2stip\nt4stit\nts3toc\nts3tor\nts3trad\nt4stran\nts3trau\nt2s3trä\nt4streu\nt2stri\nt4strop\nt2s3trü\nts2tu\nt2s1u\n1tsub\nt3sy\n4t1t\ntt1ab\ntta2be\ntt2ac\nt2t1ad\ntta6gess\ntt1ak\ntt2al\ntt2ant\ntt1art\ntta1s\ntt1ebe\ntt1eif\ntt1eis\nt3tel\ntte2la\ntte4leb\ntte4len\nttel1o\nttes1\ntte2sa\ntte2sä\nt4teti\ntt2häu\nt2t3ho\nt3ti\nt3to\ntto1s\nt3tö\nt3tro\ntt3ru\ntt3rü\ntt2sen\ntt2sor\ntts1p\ntt2spe\ntt2spr\ntt2sti\nttt4\nt3tu\ntt2un\nt3tü\n1tu\ntu1alm\ntu3an\n2tub\n3tuc\ntu2chi\n2tud\n3tue\n4tuf\ntuf2e\ntu3fen\nt3u2fer\ntuff3\n2tuh\n2tuk\nt3u2kr\ntul2a\nt2um.\n3t2ume\n2t3umf\n2t3umg\n2t3umk\n2t3umr\ntum2si\ntum2so\ntums5tr\n2t3umt\n2t3umz\n3tun.\n2t1una\n2t1und\n3tune\n2t3unf\n3tung\nt3unga\ntung4s5\n2tunif\n2t1u2nio\n2t3unt\nt1up.\ntu2r1a4g\ntu2rä\ntur1c\ntu2re.\ntu2rei\ntu2r1er\ntu2res\ntu2r1e4t\nturin1\n3turn\ntu2ro\ntu4ru\ntu2sa\ntu4schl\ntu2so\ntu3ta\n2tü\n4tüb\n3tüch\ntück2s\n3tüf\n3tüm\n3tür.\ntür1c\n3türe\n3türg\n3tür3s\n3tüten\n4tütz\n4t3v\n4t3w\ntwa2\ntwi4e\n1ty1\n3typ\nty2pa\ntys4\n4t1z\nt2za4\ntz1ag\ntz1al\ntz1ar\ntz1au\ntz1ä\nt3ze.\nt2z1e2c\nt2z1eie\nt2z1eis\ntze4n1\ntz2ene\ntz3ents\ntz1erl\ntz2ers\nt3ze2s\ntzgel2\ntz1ind\ntz1int\nt2zor\ntz2ö\ntz2th\ntz2tin\ntz1wä\ntz1wi\ntz1wu\n2ua\nu1a2b\nu1a2c\nuad4r\nu1al.\nua2lau\nu1alb\nu3alet\nu1alf\nu3a2lo\nu1alr\nu1als\nu1alt\nua2lu\nu1am\nu1ans\nu3ar.\nuara2b\nu1ars\nua3sa\nua2th\nuat2i\nu3au\nu1ay\nu1äm\nu1äu\n2u1b\nu2be2c\nu3b4i\nubi3os.\nub2l\nub3lic\nu2b3lu\nu2bop\nub1r\nub3rä\nu2b3rit\nub2san\nub2s1o\nub2spa\nu2büb\n2uc\nuc1c\nu1ce\nuch1a\nu1cha.\nuch1ä\nu1che\nu2ch1e4c\nuch1ei\nu3ches\nu1chi\nuch1il\nuch1in\nuch3l\nuch3m\nuch3n\nu2ch3r\nuch2so\nuch4spr\nuchst4\nuch4tor\nuch2t3r\nu1chu\nuch3ü\nuch1w\nu1ci\nu2ckem\nu4ckent\nu3ck2er\nu2cki\nu1cl\n2u1d\nu3d2a\nuden3s2\nuder2e\nudi3en\nuditi4\nu2don\nud3ra\nu3dru\n2u1e\nue2ck\nu2ed\nue2en\nu2eg\nu2ela\nue2le\nueli4\nue2mi\nuen1\nue2nä\nue2ner\nuenge4\nue2ni\nue2no\nuen2zu\nu2ep\nue2r3a\nue2r1ä\nu2ere\nu3ereh\nue3reig\nu3erer\nue4rerg\nue4rerk\nu3erex\nuer3g2\nu4erinn\nu3erin4t\nuer2ne\nuer4ner\nuern3s4t\nue2r3o\nu3err\nuer3sc\nuer3t2\nu3erum\nu3erunf\nu3erunt\nue2ta\nue4tek\nu3fah\nuf1ak\nuf3ar\nu3fas\nuf1au\nu2f1äs\nu2f1ä2ß\nu2f1ei\nu2f1em\nu3fen.\nu2fent\nu2f1erh\nu4ferle\nuf2ern\n2uff\nuff4l\nuf2fro\nuffs4\nuf3l\nu2fob\nufo2r\nuf1ori\nuf3r\nuf3sä\nuf4sin\nuf4so\nuf2spo\nufs3tem\nuf2t1eb\nuft3s2\nu2fum\n2u1g\nu4gabte\nug1af\nug1ak\nu2g1ap\nuga4s\nug1au\nug3d2\nu2g1ei\nu2g1erf\nu2g1erl\nugge4st\nug3hu\nu2g1l\nug3lad\nug3lo\nu3g2lö\nu4glu\nu2g3n\nugo3\nug1or\nu2gö\nu4g3reis\nug3ro\nug3rüs\nug3se\nug4ser\nug3si\nug3spa\nug4spr\nug4spu\nug5stä\nug3str\nug3stü\nu2gü\nu1h\n2uh.\nuhe3s6\nuh1la\nuh1lä\nuh2li\nuhme4\nuhr1a\nuh2rer\nuh3ri\nuh4rin\nuhrt4\nuh2ru\nuh4rü\nuh1w\n2ui\nui2ch\nu1ie\nui1em\nu3ig\nu4ige\nu1in.\nu3isch.\nu3ischs\nuisi4n\nui4s5t\nu1j\nuk2a\nu3käu\nu1ke\nu1ki\nu1k2l\nukle1i\nuk4n\nuk2ö\nu1k4r\nuk2ta\nuk2t1in\nuk2t3r\nu1ku\nuku2s\nu1l\nul1ab\nul1am\nula2s\nul1äm\nulb4\nul2dr\nuld2se\nu2l1el\nule4n\nul1erf\nul1erh\nul1erw\nule2sa\nule2t\nul1eta\nu2lex\nul3f4\nulg4\nuli2k\nul1ins\nul3ka\nul2kn\null2a\nul2les\null3s\nulm3ein\nulo2i\nul1or\nul2p1h\nul2sa\nul4sam\nuls2th\nuls3z\n2ulta\nul3th\nul4tri\nult3s\nu2lü\nul2vr\nulz2w\nu2m3a2k\num1all\num1anz\nu2m1art\nu2m1aus\nu2maut\n1um3d2\num2en\nument4s\numer2a\nu2m1erg\nu2m1erl\nu2m1erw\n1umf\n1umg\num1ins\num1ir\n1umk\n1um3l\n4umm\numm2a\numpf4li\num2p3le\n1umr\n3umsat\num4ser\num2sim\num2s1pe\num2su\num3t2\num2un\nu2m1ur\n1umz\nun1\n4un.\n2una.\n1unab\nun3ac\nun4al\nu3n2am\nu2n3an\n2un2as\nun3at\n1unda\nun4dab\n1undd\nun4dei\nun4d3erf\nund5erha\n1undf\n2undg\nun2did\n1undn\nun2dor\nun2d3r\n4unds.\nund3sp\nund3st\nun2d1um\n1undv\n1undz\nu3ne\nune2b\nune2h\nun2ei.\nun3ein\nunen2t\nun4es4\n1unget\n1ungew\nung5h\n1unglü\nun2g1r\nung3ra\nung3ri\nung4sa\nun2id\nun3ide\n1u2nif\nunik4\nun2im\nuni2r\n2unis\nun3isl\nu3n2it\n3u2niv\n2unk\nun2k1a2\nun2kei\nunks2\nunk4tit\nunk2t3r\n3unku\nunna2\nun2n3ad\nun3n2e\nuno4r\nun2os\n1unr\nuns2\n2uns.\nun3se\n1unsi\nun3sk\nun3sp\nuns4t1r\n1unt\nun3ta\nunte4ri\nun3tr\nunt3s\n2untu\nunvol2\nunvoll3\n1unw\n2unz\n2uo\nu1o2b\nu3of\nu3or.\nu1or3c\nu3ors\nuos2\nu1os.\nuote2\nu1pa\nu1pe2\nuper1\nup2fa\nu2pf2e\nu2pf1i\nu3pi\nup2pl\nup2pr\nu1pr\nup4t3a2\nupt3erg\nupt1o\nup4tr\nu1q\n2ur.\nu1ra\nu2rab\nu3raba\nura2be\nu2r3a2m\nu2r1ana\nur2anb\nu2r1ang\nur2anh\nu2r1an5s\nu2rar\nur3a4ren\nu2r3att\nu2r1au\n2u1rä\nur1än\nur3b2a\nurch1\nurd2\nur3di\n2ure\nur1eff\nu2rele\nure4n\nu4r1ep\nur1erh\nur1erw\n2urf\nurf3t\nur2gri\nurgros4\nurg3s4\nuri2c\nur1im\nur1ini\nur3ins\nur1int\nu2rinv\nurk2s\n1urlau\n4u1ro\nu3rol\nuro1s\nu1rö\nur3p\nur3sac\nur2san\nur2sau\nur2ser\nur4sin\nurst4r\nur4sw\nur3s2ze\nurt2\nu3ru\nurü2\nur2za\nur2zä\nur2zi\nur2zo\nur2z1w\n2us\nu4saf\nus4ann\nu6schent\nusch5wer\nu2s1ec\nu2s1ei\nu3seid\nu3sep\nuse1ra\nu2serp\nu2s1ese\nusi3er.\nusi5ers.\nus3kl\nu4sko\nus3oc\nu3soh\nu2s1op\nus1ou\nus3part\nu2s1pas\nu2spat\nus1pe\nu3s2pek\nus1pic\nu5s4piz\nu2spo\nus2por\nu2spu\nus4sez\nus2sof\nust3abe\nu1stal\nus3tau\nus2th\nust2in\nus3tr\nu5stras\nus6tris\nu1stu\nu2stun\nu2stur\nus2ur\nu2sü\n2u1ß\n2u1t\nut1alt\nut3a2m\nu2t1ap\nu2t1ar\nu2tär\nu3te\nut1eg\nute4ge\nute2n1\nu2tent\nuter4er\nu4t3ersa\nut2es\nut2et\nu4tev\nu4t1ex\nutfi4\nut2he\nu2thi\nu2t3ho\nu2thu\nuto1\nuto4ber\nuto3c\nut1opf\nu2tops\nut4or\nutos4\nu3tö\nut3rea\nut3rü\nut3s2a\nut2s1ä\nut4schl\nut4schm\nut4schö\nut2spa\nut3te\nut5t4l\nutts2\nutu4re\nutu5ru\nu3tü\nutz3eng\nut2zin\nut2zo\nut2z1w\n2u1u2\nuufe2\nu1ü2\n2u1v4\nu2ve.\nuve3rä\nu1w\n2u1x\nux2e\nux2o\nux3t\nu1ya\n2u1z\nuz1we\nuz3z4\n1üb\n2übc\n2übd\nübe2\nübe3c\nüber3\nüb3l\nüb3r\nüb2s3t\n2üc\nü1che\nüch3l\nüch2s1c\nücht4e\nü3cken\nück1er\nück3eri\nü4ckers\nück4spe\n2üd\nüd3a4\nü3den.\nüden4g\nü3d2ens\nüd1o4\nüd3r\nüd3s2\nüdsa1\nüd3t4\nüdwes2\nü2f1a\nü2f1ei\nüfer2\nü2f1erg\nüf2fl\nü2f1i\nüf3l\nüf2to\nü1g\nüge6leis\nü2g3l\nü2gn\nüg3s\nüg4st\nüh1a\nü1he\nü2h1ei\nü2h1eng\nü2h1erk\nü2h1erz\nüh1i\nühla2\nühl1ac\nühl2e\nüh3mo\nüh3ne\nühn2s\nüh3r2e\nühr3ei.\nüh1ro\nühr3ta\nüh1s\nühs2p\nüh3t\nüh4th\nü1hu\nüh1w\nü1k\nül1a\nül2c\nül4e\nül2la\nül2l1ei\nül2lo\nül2lö\nü1lu\nü2ment\n2ün\nü2n1a\nün2da\nün2dr\nünd3s\nünen3\nün2fa\nün2f1ei\nün2fli\nün2fr\nün2g3l\nünn2s\nün2s\nün3sc\nün3se\nün3sp\nün3str\nünt2\nü1nu\nün2za\nü1pe\nü1pi\nüp2pl\nür1a\nü2r1ei\nür2fl\nür2fr\nür4g3en4g\nü3r2o1\nürr2\nür2s\nür3sc\nür3se\nür3sp\nürt2h\nüs2a\nü2schl\nüse3h\nüse3l\nüse1s\nüs2s1c\nüss2e\nüs2st\nü2st\nüste3ne\nü1ß\n2üt\nü2t1al\nü2t3r\nüt2s1\nüt2tr\nü1v\nü1z\n2v1ab\nva1c\nval2s\n2vang\n2varb\nva1s\nv4at\nva2t3a4\nva2tei\nva2t3h\nvatik2\nva4t1in\nvati8ons.\nva2t3r\nvat3s4\nva2t1u\n2v1au\n2v1b\n2v1d\n1ve2\nve3ar\nve3b\nve3c\nve3d\nve3g\nve3h\nve4i\nveit4\nveits3\nve3la\nve4l1au\nve3le\nve3li\nve3lo\nve3ma\nve3mu\nve3nal\nven2c\nve3ne\nvenen4d\nve3ni\nve3nö\nve3o\nver1\nver3a\nve3rad\nve3rand\nve3ras\nver3b2\nverd2\nvere2\nve4rek\nverf4\nverg4\nve3ri\nve4rin\nver3k\nver3st\nvert2\nver5te\nver3u\nves1\n2ve3sc\n2ve3s2e\nves3ti\nve3ta\nvete1\nve3tr\n2veü\nve3v\nve3x2\n2v1f4\n2v1g\n2v1h\nvi3ar\nvi4a3t\nvi2c\nvid3s2t\nvie2h3a\nvi2el\nvi2er\nvie4rec\nvie2w1\nvig2\n2vii\nvi2l1a\nvi4leh\nvi2l1in\n2v1i2m\nvima2\nvi4na\nvin2s\n2v1int\nvi3sa\nvise4\nvi3s2o\nvi2sp\nvis2u\n2v1k\n2v1l2\n2v1m\n2v1n\n2v1ob\nvo3ga\nvo2gu\n3vol\nvoll1a\nvollen4\nvol6l5end\nvol2li\n2v1op\nvo2r1\nvor3a\nvor3d\nvor3e\nvor3g\nvo3ri\nvormen4\n3voy\nvö2c\n2v1p\nv2r\n2v3ra\nv3re\nv4ree\n2v3ro\n2vs\nvs2e\nv1sta\nv1steu\nv3s2z\n2v3t\nvu2et\n2vumf\n2v1v\n2v1w\n2v1z\nw2a\n1waa\nwab2bl\nwa3che\nwach6stu\nwach4t4r\nwaffe2\nwaffel3\n1wag\nwa5ge\nwa2g3n\nwa3go\n1wah\nwahl5ent\nwah4ler\nwah2li\nwai2b\n1wal\n2walb\nwal4da\n2walm\nwal2ta\nwal2to\nwalt4st\nwa3na\nwandels6\nwang4s\nwa2p\n1war2e\nware1i\nwar3ste\nwart4e\n1was\nwa3sa\nwa4scha\nwa3se\nwa3sh\nwass4e\nw2ä\n1wäh\n1wäl\n2wäng\n1wäs\nwäs2c\nwä3sche\n2w1b2\nwbu2\n2w1c\n2w1d\nwe2a\nwe2ba\n4webeb\nwe2bl\nweb3s\nwe3cke.\nwe5cken.\nwe3ckes\nwe2e4\nweed3\nwe2fl\n1weg\nwe2g1a\nwe2g3l\nwe2g3r\nweg3s4\n1weh\nwe2i\nwei4bl\n2weie\nweik4\nweis4s3p\nwei3str\nwei4tr\nwel6schl\nwel6schr\nwel2t1\nwel4t3a4\nwel6t5en6d\nwel4tr\nwen3a4\nwe3ni\nwen4k3ri\nwe2r3a\nwer2bl\n1werbu\nwerd2\n5werdens\n1werdu\nwerer2\nwer2fl\nwer4gel\nwe4r3io\n1werk.\nwer2ka\n1werke\nwer2kl\nwer2ku\nwe2rö\nwer2s\nwer2ta\nwer6t5erm\nwer2to\n1werts\n1wese\nwe2s1p\nwe4st\nwest1a\nwest3ei\nwes2th\nwest1o2\nwest3r\nwes4tu\n1wet\nwet2s\nwett3s\n2w1ey\n2w1g\n2w3h\nwi3cka\n1wid\nwi2e\nwie3l\nwien2e\nwie2st\nwik2\n1wil\nwim2ma\nwim4m3u\nwin4d3e4c\nwin2dr\nwin2e\n2wing\nwin8n7ersc\n1wi4r\nwi3s2e\nwi2sp\n1wiss\nwi3th\n1witzl\n2w1k\n2w1l\n2w1m\n2wn\nwn3s\n1wo1c\nwo2cha\nwoche4\n1woh\nwoh2le\n1wolf\nwolf4s3\nwol4ler\nwor3a\nwo2r3i\nwor2t3r\nwo4r3u\nwot2\n1wöc\nwört2h\n2w1p\nw2r\nw3ro\n2w1s\nw3s2k\nws2t\n2w1t\nwti2\nw2u\n1wuc\nwul2\nwul3se\nwun2s\n4wur.\nwur2fa\nwur2s\n1wurst\nwus2\nwus3te\n1wu4t1\n1wüh\nwül2\nwün3\n2w1w\nx1a\n1xa.\n2xa2b\n1x2ad\n1xae\nxa1fl\n1x2ag\nx3a2m\nx2anz\n1x2as\n2x1b\n2xc\nx1ce\nx1ch\nx1cl\n4x1d\n1xe\nx1e4g\n2xek\nxe2l\nxe3lei\nx1em\n3x2em.\nx2en\nxen3s2\nx2er.\nx2ere\nxers2\n3xes\n2x3eu\n2x1f\n2x1g\n2x1h\nxib4\nxi1c\nxich2\nxide2\nxi2d1em\nx1i2do\nxie3l\nxi3g\nxil1\nxil2a\nxi2lo\nxi2lu\nxin3s2\nx2is1\nxis2c\nxi2se\nxi2so2\nxis3s\nxis4tä\nxi2su\nx1i2tu\nx1j\n2x1k2\n2x2l2\nx3lä\nx3le\n2x1m\n2x1n\nx1or\n4x1p\nxpor6ter\nx1q\n2x1r\n2x3s2\n4x1t\nx2t1a\nxt2as\nxt1ä\nx2tän\nxtblo4\nx2t1e2d\nx2t1ei\nx4tent\nx2t1er2f\nx2t3ev\nxtfi4\nx2t1il2l\nxtra3b4\nx2t3ran\nxt3s2\nxt1u\nx3tur\n1xu\nxu1a\nx1u2n\nxu2s\n2xv\n2x1w\n2xy\n3xy.\n3xys\nx1z\n2y1ab\n1yac\ny1al.\ny1a2m\nyan2g\ny1ank\ny1ät\ny1b\ny1c2\ny2chi\ny3chis\nych3n\ny1d4\ny1e\ny2ef\nyen4n\ny2ere\ny2es.\nyes2p\nye2th\ny1f2\ny1g\nygi2\nygie5\nyg2l\ny1h\nyhr2\ny1i4\ny1j\ny1k2\nyke3n\nyk3s2\ny1l\ny2l3a2m\nyl4ante\nyl3c\ny4le.\nyli4n\nyloni1\nyl3s2\ny2l1u\nyma4t\nym3p4\nympi1\ny2n1o\nyno4d\nynt2\ny1nu\ny1of\nyom2\nyon4i\ny1ont\ny1ou\ny1p\nypa2\nyp3an\nype2\ny2pf\ny3ph\ny2p1in\nypo3\ny4p3s\ny1r\ny3r2e\ny3ri\nyri2a\nyri1e\ny3r4o\nyrr2\nys2an\ny3s2c\nyse1\ny3s2h\ny4s3l\nysme3\nys2po\nys1pr\nys3t4\ny1s4ty\ny2s1u2\ny3s2z\ny1t2\ny2te.\ny2tes\ny3to1\nyu2r\nyure3\ny1v\ny1w\ny1y\ny1z2\n2z3a2b\nzab3l\nza1c\nz1a2d\nza3de\n2z1af\nza3gr\n3zah\n2z3a2k\nzale3\n2z1all\n2z1am\n3zambiq\nz1an\nza2na\n2z3anf\n3zani\n2z3anl\n2zarb\n2zarc\nz1arm\nz1arti\nzar2tr\n2z1arz\nz1as\nza1st4\n2z3at3\n3zaub\nz1au2f\nz3aug\n3zaun\nzä2\n2z1äc\n3z2äh\n2z1äm\nz1ärg\nz1ärm\n4z3b4\nzbü1b\nzbübe3\n2z3c\n2z3d2\nzdan2\nzdä1\n2z1e2ben\n2zecho\n2z1eck\nze1e\n2z1eff\nzeik4\nzei3la\nzeile4\n2z1ein\nzei3s4\nzeist4\nzei2t1a\nzeit5end\nzei4t3er\nzei2tr\nze2l1a2\nze2len\nze2l1er\nze2l1in\nzell2a\nzel3sz\nzel3t2h\nzelu2\n2z1emp\n5zen.\nze4n3ac\nzen3n\nze2no\nzens2e\nzen4sem\n3zent\nzent3s\nzen4zer\nz2er.\nze2r3a\nze2re2b\n2z1ergä\n4z3ergeb\nz3erhal\n2zerhö\nzerin4t\nzerk2\nz2erl.\n2zerlö\nz2ern\nzer4neb\nzer4n3ei\nze2ro\n2z1erq\nzers2\n2z1ersa\n4z3erste\nzert1a4\nzer4t3ag\nzert4an\nzer6tere\nzer4tin\nzer6t5rau\n4zerwei\n2z1erz\n3z2erza\nze2sä\nze3sc\nzes1e\nzes1i\nze3sku\nze2sp\nzessen4\nzes6s5end\nzes2sp\nzes2st\nze2s3t\nze3sta\nze2tr\n2zetts\n2z1ex\n2z1f4\n2z1g2\nzger2a\n2z1h\nz2hen\nzhir3\nzi3alo\nzi3ar\nzid3r\nzi1erh\nziers1\nzi1es.\nzil2e\n2z1imp\nzin2e\nzin4er\n2z1inf\n2z1inh\nzin1it\nzin2sa\nzin4ser\n4zinsuf\n2z1inv\nzi2o3\nzi3op\nzirk2\nzirk6s\nzi3s2z\nzi1t2h\n2z1j\n2z3k4\n2z1l2\n2z1m2\nzme2e\n2z3n4\n2z1ob\n2z1of\nzo2gl\n2z1oh\n3zol\nzon4ter\nzo2o\n2zope\nz1or\nzo2ri\nzor4ne\n2z1osz\n2z3ot\n2zö2f\nz1öl\n2z3p4\n2z1q\n2z3r2\n4z1s2\nz3sa\nz3sh\nz3sk\nz3sz\n2z1t\nz2t1au\nz4tehe\nz3t2her\nzt3ho\nzt1ins\nz3tö\nzt3rec\nzt3s2\nz3tü\nzu1\nzu3a\nzub4\nzu4ch\nzu3cke\nzud4\nzudi4\nzu2el\nzu3f4\nzu2g1ar\nzu4gent\nzu3gl\nzug1un\n2z1uhr\nzu3k\n2z1um.\nzumen2\n2zumf\n2zumg\n2zuml\n2z1ums\nzun2e\nzung4\n2zunt\nzup2fi\nzu3r2a\nz1urk\n2z1url\n2z1urs\n2z1urt\nzu3s4\nzu5t2\nzuz2\n2züb\nzür1c\n2z1v\nzw2\nz1wac\n4zwah\nzwan2d1\nz2wang\nz1war\n2zwas\n4zwäl\n2zweg\nz2weig\nz1weis\n2z1wel\n2z1wen\n2z1wer\nz2werg\n2z1wes\n2zwet\n4zwir\nz2wit\n2z1wo\nz1wör\nz1wur\n2z1wü\n4z1z\nz3z4a\nzzi1s4\nz3z2o\nzz2ö\n"
  },
  {
    "path": "test/hyph-en-us.pat",
    "content": "% version of 2005-05-30.\n% Patterns of March 1, 1990.\n%\n% Copyright (C) 1990, 2004, 2005 Gerard D.C. Kuiken.\n% Copying and distribution of this file, with or without modification,\n% are permitted in any medium without royalty provided the copyright\n% notice and this notice are preserved.\n%\n% Needs extended pattern memory.\n% Hyphenation trie becomes 7283 with 377 ops.\n%\n% These patterns are based on the Hyphenation Exception Log\n% published in TUGboat, Volume 10 (1989), No. 3, pp. 337-341,\n% and a large number of incorrectly hyphenated words not yet published.\n% If added to Liang's before the closing bracket } of \\patterns,\n% the patterns run errorfree as far as known at this moment.\n%\n% These patterns find all admissible hyphens of the words in\n% the Exception Log.  ushyph2.tex is a smaller set.\n%\n% Please send bugs or suggestions to tex-live (at) tug.org.\n%\n.ach4\n.ad4der\n.af1t\n.al3t\n.am5at\n.an5c\n.ang4\n.ani5m\n.ant4\n.an3te\n.anti5s\n.ar5s\n.ar4tie\n.ar4ty\n.as3c\n.as1p\n.as1s\n.aster5\n.atom5\n.au1d\n.av4i\n.awn4\n.ba4g\n.ba5na\n.bas4e\n.ber4\n.be5ra\n.be3sm\n.be5sto\n.bri2\n.but4ti\n.cam4pe\n.can5c\n.capa5b\n.car5ol\n.ca4t\n.ce4la\n.ch4\n.chill5i\n.ci2\n.cit5r\n.co3e\n.co4r\n.cor5ner\n.de4moi\n.de3o\n.de3ra\n.de3ri\n.des4c\n.dictio5\n.do4t\n.du4c\n.dumb5\n.earth5\n.eas3i\n.eb4\n.eer4\n.eg2\n.el5d\n.el3em\n.enam3\n.en3g\n.en3s\n.eq5ui5t\n.er4ri\n.es3\n.eu3\n.eye5\n.fes3\n.for5mer\n.ga2\n.ge2\n.gen3t4\n.ge5og\n.gi5a\n.gi4b\n.go4r\n.hand5i\n.han5k\n.he2\n.hero5i\n.hes3\n.het3\n.hi3b\n.hi3er\n.hon5ey\n.hon3o\n.hov5\n.id4l\n.idol3\n.im3m\n.im5pin\n.in1\n.in3ci\n.ine2\n.in2k\n.in3s\n.ir5r\n.is4i\n.ju3r\n.la4cy\n.la4m\n.lat5er\n.lath5\n.le2\n.leg5e\n.len4\n.lep5\n.lev1\n.li4g\n.lig5a\n.li2n\n.li3o\n.li4t\n.mag5a5\n.mal5o\n.man5a\n.mar5ti\n.me2\n.mer3c\n.me5ter\n.mis1\n.mist5i\n.mon3e\n.mo3ro\n.mu5ta\n.muta5b\n.ni4c\n.od2\n.odd5\n.of5te\n.or5ato\n.or3c\n.or1d\n.or3t\n.os3\n.os4tl\n.oth3\n.out3\n.ped5al\n.pe5te\n.pe5tit\n.pi4e\n.pio5n\n.pi2t\n.pre3m\n.ra4c\n.ran4t\n.ratio5na\n.ree2\n.re5mit\n.res2\n.re5stat\n.ri4g\n.rit5u\n.ro4q\n.ros5t\n.row5d\n.ru4d\n.sci3e\n.self5\n.sell5\n.se2n\n.se5rie\n.sh2\n.si2\n.sing4\n.st4\n.sta5bl\n.sy2\n.ta4\n.te4\n.ten5an\n.th2\n.ti2\n.til4\n.tim5o5\n.ting4\n.tin5k\n.ton4a\n.to4p\n.top5i\n.tou5s\n.trib5ut\n.un1a\n.un3ce\n.under5\n.un1e\n.un5k\n.un5o\n.un3u\n.up3\n.ure3\n.us5a\n.ven4de\n.ve5ra\n.wil5i\n.ye4\n4ab.\na5bal\na5ban\nabe2\nab5erd\nabi5a\nab5it5ab\nab5lat\nab5o5liz\n4abr\nab5rog\nab3ul\na4car\nac5ard\nac5aro\na5ceou\nac1er\na5chet\n4a2ci\na3cie\nac1in\na3cio\nac5rob\nact5if\nac3ul\nac4um\na2d\nad4din\nad5er.\n2adi\na3dia\nad3ica\nadi4er\na3dio\na3dit\na5diu\nad4le\nad3ow\nad5ran\nad4su\n4adu\na3duc\nad5um\nae4r\naeri4e\na2f\naff4\na4gab\naga4n\nag5ell\nage4o\n4ageu\nag1i\n4ag4l\nag1n\na2go\n3agog\nag3oni\na5guer\nag5ul\na4gy\na3ha\na3he\nah4l\na3ho\nai2\na5ia\na3ic.\nai5ly\na4i4n\nain5in\nain5o\nait5en\na1j\nak1en\nal5ab\nal3ad\na4lar\n4aldi\n2ale\nal3end\na4lenti\na5le5o\nal1i\nal4ia.\nali4e\nal5lev\n4allic\n4alm\na5log.\na4ly.\n4alys\n5a5lyst\n5alyt\n3alyz\n4ama\nam5ab\nam3ag\nama5ra\nam5asc\na4matis\na4m5ato\nam5era\nam3ic\nam5if\nam5ily\nam1in\nami4no\na2mo\na5mon\namor5i\namp5en\na2n\nan3age\n3analy\na3nar\nan3arc\nanar4i\na3nati\n4and\nande4s\nan3dis\nan1dl\nan4dow\na5nee\na3nen\nan5est.\na3neu\n2ang\nang5ie\nan1gl\na4n1ic\na3nies\nan3i3f\nan4ime\na5nimi\na5nine\nan3io\na3nip\nan3ish\nan3it\na3niu\nan4kli\n5anniz\nano4\nan5ot\nanoth5\nan2sa\nan4sco\nan4sn\nan2sp\nans3po\nan4st\nan4sur\nantal4\nan4tie\n4anto\nan2tr\nan4tw\nan3ua\nan3ul\na5nur\n4ao\napar4\nap5at\nap5ero\na3pher\n4aphi\na4pilla\nap5illar\nap3in\nap3ita\na3pitu\na2pl\napoc5\nap5ola\napor5i\napos3t\naps5es\na3pu\naque5\n2a2r\nar3act\na5rade\nar5adis\nar3al\na5ramete\naran4g\nara3p\nar4at\na5ratio\nar5ativ\na5rau\nar5av4\naraw4\narbal4\nar4chan\nar5dine\nar4dr\nar5eas\na3ree\nar3ent\na5ress\nar4fi\nar4fl\nar1i\nar5ial\nar3ian\na3riet\nar4im\nar5inat\nar3io\nar2iz\nar2mi\nar5o5d\na5roni\na3roo\nar2p\nar3q\narre4\nar4sa\nar2sh\n4as.\nas4ab\nas3ant\nashi4\na5sia.\na3sib\na3sic\n5a5si4t\nask3i\nas4l\na4soc\nas5ph\nas4sh\nas3ten\nas1tr\nasur5a\na2ta\nat3abl\nat5ac\nat3alo\nat5ap\nate5c\nat5ech\nat3ego\nat3en.\nat3era\nater5n\na5terna\nat3est\nat5ev\n4ath\nath5em\na5then\nat4ho\nath5om\n4ati.\na5tia\nat5i5b\nat1ic\nat3if\nation5ar\nat3itu\na4tog\na2tom\nat5omiz\na4top\na4tos\na1tr\nat5rop\nat4sk\nat4tag\nat5te\nat4th\na2tu\nat5ua\nat5ue\nat3ul\nat3ura\na2ty\nau4b\naugh3\nau3gu\nau4l2\naun5d\nau3r\nau5sib\naut5en\nau1th\na2va\nav3ag\na5van\nave4no\nav3era\nav5ern\nav5ery\nav1i\navi4er\nav3ig\nav5oc\na1vor\n3away\naw3i\naw4ly\naws4\nax4ic\nax4id\nay5al\naye4\nays4\nazi4er\nazz5i\n5ba.\nbad5ger\nba4ge\nbal1a\nban5dag\nban4e\nban3i\nbarbi5\nbari4a\nbas4si\n1bat\nba4z\n2b1b\nb2be\nb3ber\nbbi4na\n4b1d\n4be.\nbeak4\nbeat3\n4be2d\nbe3da\nbe3de\nbe3di\nbe3gi\nbe5gu\n1bel\nbe1li\nbe3lo\n4be5m\nbe5nig\nbe5nu\n4bes4\nbe3sp\nbe5str\n3bet\nbet5iz\nbe5tr\nbe3tw\nbe3w\nbe5yo\n2bf\n4b3h\nbi2b\nbi4d\n3bie\nbi5en\nbi4er\n2b3if\n1bil\nbi3liz\nbina5r4\nbin4d\nbi5net\nbi3ogr\nbi5ou\nbi2t\n3bi3tio\nbi3tr\n3bit5ua\nb5itz\nb1j\nbk4\nb2l2\nblath5\nb4le.\nblen4\n5blesp\nb3lis\nb4lo\nblun4t\n4b1m\n4b3n\nbne5g\n3bod\nbod3i\nbo4e\nbol3ic\nbom4bi\nbon4a\nbon5at\n3boo\n5bor.\n4b1ora\nbor5d\n5bore\n5bori\n5bos4\nb5ota\nboth5\nbo4to\nbound3\n4bp\n4brit\nbroth3\n2b5s2\nbsor4\n2bt\nbt4l\nb4to\nb3tr\nbuf4fer\nbu4ga\nbu3li\nbumi4\nbu4n\nbunt4i\nbu3re\nbus5ie\nbuss4e\n5bust\n4buta\n3butio\nb5uto\nb1v\n4b5w\n5by.\nbys4\n1ca\ncab3in\nca1bl\ncach4\nca5den\n4cag4\n2c5ah\nca3lat\ncal4la\ncall5in\n4calo\ncan5d\ncan4e\ncan4ic\ncan5is\ncan3iz\ncan4ty\ncany4\nca5per\ncar5om\ncast5er\ncas5tig\n4casy\nca4th\n4cativ\ncav5al\nc3c\nccha5\ncci4a\nccompa5\nccon4\nccou3t\n2ce.\n4ced.\n4ceden\n3cei\n5cel.\n3cell\n1cen\n3cenc\n2cen4e\n4ceni\n3cent\n3cep\nce5ram\n4cesa\n3cessi\nces5si5b\nces5t\ncet4\nc5e4ta\ncew4\n2ch\n4ch.\n4ch3ab\n5chanic\nch5a5nis\nche2\ncheap3\n4ched\nche5lo\n3chemi\nch5ene\nch3er.\nch3ers\n4ch1in\n5chine.\nch5iness\n5chini\n5chio\n3chit\nchi2z\n3cho2\nch4ti\n1ci\n3cia\nci2a5b\ncia5r\nci5c\n4cier\n5cific.\n4cii\nci4la\n3cili\n2cim\n2cin\nc4ina\n3cinat\ncin3em\nc1ing\nc5ing.\n5cino\ncion4\n4cipe\nci3ph\n4cipic\n4cista\n4cisti\n2c1it\ncit3iz\n5ciz\nck1\nck3i\n1c4l4\n4clar\nc5laratio\n5clare\ncle4m\n4clic\nclim4\ncly4\nc5n\n1co\nco5ag\ncoe2\n2cog\nco4gr\ncoi4\nco3inc\ncol5i\n5colo\ncol3or\ncom5er\ncon4a\nc4one\ncon3g\ncon5t\nco3pa\ncop3ic\nco4pl\n4corb\ncoro3n\ncos4e\ncov1\ncove4\ncow5a\ncoz5e\nco5zi\nc1q\ncras5t\n5crat.\n5cratic\ncre3at\n5cred\n4c3reta\ncre4v\ncri2\ncri5f\nc4rin\ncris4\n5criti\ncro4pl\ncrop5o\ncros4e\ncru4d\n4c3s2\n2c1t\ncta4b\nct5ang\nc5tant\nc2te\nc3ter\nc4ticu\nctim3i\nctu4r\nc4tw\ncud5\nc4uf\nc4ui\ncu5ity\n5culi\ncul4tis\n3cultu\ncu2ma\nc3ume\ncu4mi\n3cun\ncu3pi\ncu5py\ncur5a4b\ncu5ria\n1cus\ncuss4i\n3c4ut\ncu4tie\n4c5utiv\n4cutr\n1cy\ncze4\n1d2a\n5da.\n2d3a4b\ndach4\n4daf\n2dag\nda2m2\ndan3g\ndard5\ndark5\n4dary\n3dat\n4dativ\n4dato\n5dav4\ndav5e\n5day\nd1b\nd5c\nd1d4\n2de.\ndeaf5\ndeb5it\nde4bon\ndecan4\nde4cil\nde5com\n2d1ed\n4dee.\nde5if\ndeli4e\ndel5i5q\nde5lo\nd4em\n5dem.\n3demic\ndem5ic.\nde5mil\nde4mons\ndemor5\n1den\nde4nar\nde3no\ndenti5f\nde3nu\nde1p\nde3pa\ndepi4\nde2pu\nd3eq\nd4erh\n5derm\ndern5iz\nder5s\ndes2\nd2es.\nde1sc\nde2s5o\ndes3ti\nde3str\nde4su\nde1t\nde2to\nde1v\ndev3il\n4dey\n4d1f\nd4ga\nd3ge4t\ndg1i\nd2gy\nd1h2\n5di.\n1d4i3a\ndia5b\ndi4cam\nd4ice\n3dict\n3did\n5di3en\nd1if\ndi3ge\ndi4lato\nd1in\n1dina\n3dine.\n5dini\ndi5niz\n1dio\ndio5g\ndi4pl\ndir2\ndi1re\ndirt5i\ndis1\n5disi\nd4is3t\nd2iti\n1di1v\nd1j\nd5k2\n4d5la\n3dle.\n3dled\n3dles.\n4dless\n2d3lo\n4d5lu\n2dly\nd1m\n4d1n4\n1do\n3do.\ndo5de\n5doe\n2d5of\nd4og\ndo4la\ndoli4\ndo5lor\ndom5iz\ndo3nat\ndoni4\ndoo3d\ndop4p\nd4or\n3dos\n4d5out\ndo4v\n3dox\nd1p\n1dr\ndrag5on\n4drai\ndre4\ndrea5r\n5dren\ndri4b\ndril4\ndro4p\n4drow\n5drupli\n4dry\n2d1s2\nds4p\nd4sw\nd4sy\nd2th\n1du\nd1u1a\ndu2c\nd1uca\nduc5er\n4duct.\n4ducts\ndu5el\ndu4g\nd3ule\ndum4be\ndu4n\n4dup\ndu4pe\nd1v\nd1w\nd2y\n5dyn\ndy4se\ndys5p\ne1a4b\ne3act\nead1\nead5ie\nea4ge\nea5ger\nea4l\neal5er\neal3ou\neam3er\ne5and\near3a\near4c\near5es\near4ic\near4il\near5k\near2t\neart3e\nea5sp\ne3ass\neast3\nea2t\neat5en\neath3i\ne5atif\ne4a3tu\nea2v\neav3en\neav5i\neav5o\n2e1b\ne4bel.\ne4bels\ne4ben\ne4bit\ne3br\ne4cad\necan5c\necca5\ne1ce\nec5essa\nec2i\ne4cib\nec5ificat\nec5ifie\nec5ify\nec3im\neci4t\ne5cite\ne4clam\ne4clus\ne2col\ne4comm\ne4compe\ne4conc\ne2cor\nec3ora\neco5ro\ne1cr\ne4crem\nec4tan\nec4te\ne1cu\ne4cul\nec3ula\n2e2da\n4ed3d\ne4d1er\nede4s\n4edi\ne3dia\ned3ib\ned3ica\ned3im\ned1it\nedi5z\n4edo\ne4dol\nedon2\ne4dri\ne4dul\ned5ulo\nee2c\need3i\nee2f\neel3i\nee4ly\nee2m\nee4na\nee4p1\nee2s4\neest4\nee4ty\ne5ex\ne1f\ne4f3ere\n1eff\ne4fic\n5efici\nefil4\ne3fine\nef5i5nite\n3efit\nefor5es\ne4fuse.\n4egal\neger4\neg5ib\neg4ic\neg5ing\ne5git5\neg5n\ne4go.\ne4gos\neg1ul\ne5gur\n5egy\ne1h4\neher4\nei2\ne5ic\nei5d\neig2\nei5gl\ne3imb\ne3inf\ne1ing\ne5inst\neir4d\neit3e\nei3th\ne5ity\ne1j\ne4jud\nej5udi\neki4n\nek4la\ne1la\ne4la.\ne4lac\nelan4d\nel5ativ\ne4law\nelaxa4\ne3lea\nel5ebra\n5elec\ne4led\nel3ega\ne5len\ne4l1er\ne1les\nel2f\nel2i\ne3libe\ne4l5ic.\nel3ica\ne3lier\nel5igib\ne5lim\ne4l3ing\ne3lio\ne2lis\nel5ish\ne3liv3\n4ella\nel4lab\nello4\ne5loc\nel5og\nel3op.\nel2sh\nel4ta\ne5lud\nel5ug\ne4mac\ne4mag\ne5man\nem5ana\nem5b\ne1me\ne2mel\ne4met\nem3ica\nemi4e\nem5igra\nem1in2\nem5ine\nem3i3ni\ne4mis\nem5ish\ne5miss\nem3iz\n5emniz\nemo4g\nemoni5o\nem3pi\ne4mul\nem5ula\nemu3n\ne3my\nen5amo\ne4nant\nench4er\nen3dic\ne5nea\ne5nee\nen3em\nen5ero\nen5esi\nen5est\nen3etr\ne3new\nen5ics\ne5nie\ne5nil\ne3nio\nen3ish\nen3it\ne5niu\n5eniz\n4enn\n4eno\neno4g\ne4nos\nen3ov\nen4sw\nent5age\n4enthes\nen3ua\nen5uf\ne3ny.\n4en3z\ne5of\neo2g\ne4oi4\ne3ol\neop3ar\ne1or\neo3re\neo5rol\neos4\ne4ot\neo4to\ne5out\ne5ow\ne2pa\ne3pai\nep5anc\ne5pel\ne3pent\nep5etitio\nephe4\ne4pli\ne1po\ne4prec\nep5reca\ne4pred\nep3reh\ne3pro\ne4prob\nep4sh\nep5ti5b\ne4put\nep5uta\ne1q\nequi3l\ne4q3ui3s\ner1a\nera4b\n4erand\ner3ar\n4erati.\n2erb\ner4bl\ner3ch\ner4che\n2ere.\ne3real\nere5co\nere3in\ner5el.\ner3emo\ner5ena\ner5ence\n4erene\ner3ent\nere4q\ner5ess\ner3est\neret4\ner1h\ner1i\ne1ria4\n5erick\ne3rien\neri4er\ner3ine\ne1rio\n4erit\ner4iu\neri4v\ne4riva\ner3m4\ner4nis\n4ernit\n5erniz\ner3no\n2ero\ner5ob\ne5roc\nero4r\ner1ou\ner1s\ner3set\nert3er\n4ertl\ner3tw\n4eru\neru4t\n5erwau\ne1s4a\ne4sage.\ne4sages\nes2c\ne2sca\nes5can\ne3scr\nes5cu\ne1s2e\ne2sec\nes5ecr\nes5enc\ne4sert.\ne4serts\ne4serva\n4esh\ne3sha\nesh5en\ne1si\ne2sic\ne2sid\nes5iden\nes5igna\ne2s5im\nes4i4n\nesis4te\nesi4u\ne5skin\nes4mi\ne2sol\nes3olu\ne2son\nes5ona\ne1sp\nes3per\nes5pira\nes4pre\n2ess\nes4si4b\nestan4\nes3tig\nes5tim\n4es2to\ne3ston\n2estr\ne5stro\nestruc5\ne2sur\nes5urr\nes4w\neta4b\neten4d\ne3teo\nethod3\net1ic\ne5tide\netin4\neti4no\ne5tir\ne5titio\net5itiv\n4etn\net5ona\ne3tra\ne3tre\net3ric\net5rif\net3rog\net5ros\net3ua\net5ym\net5z\n4eu\ne5un\ne3up\neu3ro\neus4\neute4\neuti5l\neu5tr\neva2p5\ne2vas\nev5ast\ne5vea\nev3ell\nevel3o\ne5veng\neven4i\nev1er\ne5verb\ne1vi\nev3id\nevi4l\ne4vin\nevi4v\ne5voc\ne5vu\ne1wa\ne4wag\ne5wee\ne3wh\newil5\new3ing\ne3wit\n1exp\n5eyc\n5eye.\neys4\n1fa\nfa3bl\nfab3r\nfa4ce\n4fag\nfain4\nfall5e\n4fa4ma\nfam5is\n5far\nfar5th\nfa3ta\nfa3the\n4fato\nfault5\n4f5b\n4fd\n4fe.\nfeas4\nfeath3\nfe4b\n4feca\n5fect\n2fed\nfe3li\nfe4mo\nfen2d\nfend5e\nfer1\n5ferr\nfev4\n4f1f\nf4fes\nf4fie\nf5fin.\nf2f5is\nf4fly\nf2fy\n4fh\n1fi\nfi3a\n2f3ic.\n4f3ical\nf3ican\n4ficate\nf3icen\nfi3cer\nfic4i\n5ficia\n5ficie\n4fics\nfi3cu\nfi5del\nfight5\nfil5i\nfill5in\n4fily\n2fin\n5fina\nfin2d5\nfi2ne\nf1in3g\nfin4n\nfis4ti\nf4l2\nf5less\nflin4\nflo3re\nf2ly5\n4fm\n4fn\n1fo\n5fon\nfon4de\nfon4t\nfo2r\nfo5rat\nfor5ay\nfore5t\nfor4i\nfort5a\nfos5\n4f5p\nfra4t\nf5rea\nfres5c\nfri2\nfril4\nfrol5\n2f3s\n2ft\nf4to\nf2ty\n3fu\nfu5el\n4fug\nfu4min\nfu5ne\nfu3ri\nfusi4\nfus4s\n4futa\n1fy\n1ga\ngaf4\n5gal.\n3gali\nga3lo\n2gam\nga5met\ng5amo\ngan5is\nga3niz\ngani5za\n4gano\ngar5n4\ngass4\ngath3\n4gativ\n4gaz\ng3b\ngd4\n2ge.\n2ged\ngeez4\ngel4in\nge5lis\nge5liz\n4gely\n1gen\nge4nat\nge5niz\n4geno\n4geny\n1geo\nge3om\ng4ery\n5gesi\ngeth5\n4geto\nge4ty\nge4v\n4g1g2\ng2ge\ng3ger\ngglu5\nggo4\ngh3in\ngh5out\ngh4to\n5gi.\n1gi4a\ngia5r\ng1ic\n5gicia\ng4ico\ngien5\n5gies.\ngil4\ng3imen\n3g4in.\ngin5ge\n5g4ins\n5gio\n3gir\ngir4l\ng3isl\ngi4u\n5giv\n3giz\ngl2\ngla4\nglad5i\n5glas\n1gle\ngli4b\ng3lig\n3glo\nglo3r\ng1m\ng4my\ngn4a\ng4na.\ngnet4t\ng1ni\ng2nin\ng4nio\ng1no\ng4non\n1go\n3go.\ngob5\n5goe\n3g4o4g\ngo3is\ngon2\n4g3o3na\ngondo5\ngo3ni\n5goo\ngo5riz\ngor5ou\n5gos.\ngov1\ng3p\n1gr\n4grada\ng4rai\ngran2\n5graph.\ng5rapher\n5graphic\n4graphy\n4gray\ngre4n\n4gress.\n4grit\ng4ro\ngruf4\ngs2\ng5ste\ngth3\ngu4a\n3guard\n2gue\n5gui5t\n3gun\n3gus\n4gu4t\ng3w\n1gy\n2g5y3n\ngy5ra\nh3ab4l\nhach4\nhae4m\nhae4t\nh5agu\nha3la\nhala3m\nha4m\nhan4ci\nhan4cy\n5hand.\nhan4g\nhang5er\nhang5o\nh5a5niz\nhan4k\nhan4te\nhap3l\nhap5t\nha3ran\nha5ras\nhar2d\nhard3e\nhar4le\nharp5en\nhar5ter\nhas5s\nhaun4\n5haz\nhaz3a\nh1b\n1head\n3hear\nhe4can\nh5ecat\nh4ed\nhe5do5\nhe3l4i\nhel4lis\nhel4ly\nh5elo\nhem4p\nhe2n\nhena4\nhen5at\nheo5r\nhep5\nh4era\nhera3p\nher4ba\nhere5a\nh3ern\nh5erou\nh3ery\nh1es\nhe2s5p\nhe4t\nhet4ed\nheu4\nh1f\nh1h\nhi5an\nhi4co\nhigh5\nh4il2\nhimer4\nh4ina\nhion4e\nhi4p\nhir4l\nhi3ro\nhir4p\nhir4r\nhis3el\nhis4s\nhith5er\nhi2v\n4hk\n4h1l4\nhlan4\nh2lo\nhlo3ri\n4h1m\nhmet4\n2h1n\nh5odiz\nh5ods\nho4g\nhoge4\nhol5ar\n3hol4e\nho4ma\nhome3\nhon4a\nho5ny\n3hood\nhoon4\nhor5at\nho5ris\nhort3e\nho5ru\nhos4e\nho5sen\nhos1p\n1hous\nhouse3\nhov5el\n4h5p\n4hr4\nhree5\nhro5niz\nhro3po\n4h1s2\nh4sh\nh4tar\nht1en\nht5es\nh4ty\nhu4g\nhu4min\nhun5ke\nhun4t\nhus3t4\nhu4t\nh1w\nh4wart\nhy3pe\nhy3ph\nhy2s\n2i1a\ni2al\niam4\niam5ete\ni2an\n4ianc\nian3i\n4ian4t\nia5pe\niass4\ni4ativ\nia4tric\ni4atu\nibe4\nib3era\nib5ert\nib5ia\nib3in\nib5it.\nib5ite\ni1bl\nib3li\ni5bo\ni1br\ni2b5ri\ni5bun\n4icam\n5icap\n4icar\ni4car.\ni4cara\nicas5\ni4cay\niccu4\n4iceo\n4ich\n2ici\ni5cid\nic5ina\ni2cip\nic3ipa\ni4cly\ni2c5oc\n4i1cr\n5icra\ni4cry\nic4te\nictu2\nic4t3ua\nic3ula\nic4um\nic5uo\ni3cur\n2id\ni4dai\nid5anc\nid5d\nide3al\nide4s\ni2di\nid5ian\nidi4ar\ni5die\nid3io\nidi5ou\nid1it\nid5iu\ni3dle\ni4dom\nid3ow\ni4dr\ni2du\nid5uo\n2ie4\nied4e\n5ie5ga\nield3\nien5a4\nien4e\ni5enn\ni3enti\ni1er.\ni3esc\ni1est\ni3et\n4if.\nif5ero\niff5en\nif4fr\n4ific.\ni3fie\ni3fl\n4ift\n2ig\niga5b\nig3era\night3i\n4igi\ni3gib\nig3il\nig3in\nig3it\ni4g4l\ni2go\nig3or\nig5ot\ni5gre\nigu5i\nig1ur\ni3h\n4i5i4\ni3j\n4ik\ni1la\nil3a4b\ni4lade\ni2l5am\nila5ra\ni3leg\nil1er\nilev4\nil5f\nil1i\nil3ia\nil2ib\nil3io\nil4ist\n2ilit\nil2iz\nill5ab\n4iln\nil3oq\nil4ty\nil5ur\nil3v\ni4mag\nim3age\nima5ry\nimenta5r\n4imet\nim1i\nim5ida\nimi5le\ni5mini\n4imit\nim4ni\ni3mon\ni2mu\nim3ula\n2in.\ni4n3au\n4inav\nincel4\nin3cer\n4ind\nin5dling\n2ine\ni3nee\niner4ar\ni5ness\n4inga\n4inge\nin5gen\n4ingi\nin5gling\n4ingo\n4ingu\n2ini\ni5ni.\ni4nia\nin3io\nin1is\ni5nite.\n5initio\nin3ity\n4ink\n4inl\n2inn\n2i1no\ni4no4c\nino4s\ni4not\n2ins\nin3se\ninsur5a\n2int.\n2in4th\nin1u\ni5nus\n4iny\n2io\n4io.\nioge4\nio2gr\ni1ol\nio4m\nion3at\nion4ery\nion3i\nio5ph\nior3i\ni4os\nio5th\ni5oti\nio4to\ni4our\n2ip\nipe4\niphras4\nip3i\nip4ic\nip4re4\nip3ul\ni3qua\niq5uef\niq3uid\niq3ui3t\n4ir\ni1ra\nira4b\ni4rac\nird5e\nire4de\ni4ref\ni4rel4\ni4res\nir5gi\nir1i\niri5de\nir4is\niri3tu\n5i5r2iz\nir4min\niro4g\n5iron.\nir5ul\n2is.\nis5ag\nis3ar\nisas5\n2is1c\nis3ch\n4ise\nis3er\n3isf\nis5han\nis3hon\nish5op\nis3ib\nisi4d\ni5sis\nis5itiv\n4is4k\nislan4\n4isms\ni2so\niso5mer\nis1p\nis2pi\nis4py\n4is1s\nis4sal\nissen4\nis4ses\nis4ta.\nis1te\nis1ti\nist4ly\n4istral\ni2su\nis5us\n4ita.\nita4bi\ni4tag\n4ita5m\ni3tan\ni3tat\n2ite\nit3era\ni5teri\nit4es\n2ith\ni1ti\n4itia\n4i2tic\nit3ica\n5i5tick\nit3ig\nit5ill\ni2tim\n2itio\n4itis\ni4tism\ni2t5o5m\n4iton\ni4tram\nit5ry\n4itt\nit3uat\ni5tud\nit3ul\n4itz.\ni1u\n2iv\niv3ell\niv3en.\ni4v3er.\ni4vers.\niv5il.\niv5io\niv1it\ni5vore\niv3o3ro\ni4v3ot\n4i5w\nix4o\n4iy\n4izar\nizi4\n5izont\n5ja\njac4q\nja4p\n1je\njer5s\n4jestie\n4jesty\njew3\njo4p\n5judg\n3ka.\nk3ab\nk5ag\nkais4\nkal4\nk1b\nk2ed\n1kee\nke4g\nke5li\nk3en4d\nk1er\nkes4\nk3est.\nke4ty\nk3f\nkh4\nk1i\n5ki.\n5k2ic\nk4ill\nkilo5\nk4im\nk4in.\nkin4de\nk5iness\nkin4g\nki4p\nkis4\nk5ish\nkk4\nk1l\n4kley\n4kly\nk1m\nk5nes\n1k2no\nko5r\nkosh4\nk3ou\nkro5n\n4k1s2\nk4sc\nks4l\nk4sy\nk5t\nk1w\nlab3ic\nl4abo\nlaci4\nl4ade\nla3dy\nlag4n\nlam3o\n3land\nlan4dl\nlan5et\nlan4te\nlar4g\nlar3i\nlas4e\nla5tan\n4lateli\n4lativ\n4lav\nla4v4a\n2l1b\nlbin4\n4l1c2\nlce4\nl3ci\n2ld\nl2de\nld4ere\nld4eri\nldi4\nld5is\nl3dr\nl4dri\nle2a\nle4bi\nleft5\n5leg.\n5legg\nle4mat\nlem5atic\n4len.\n3lenc\n5lene.\n1lent\nle3ph\nle4pr\nlera5b\nler4e\n3lerg\n3l4eri\nl4ero\nles2\nle5sco\n5lesq\n3less\n5less.\nl3eva\nlev4er.\nlev4era\nlev4ers\n3ley\n4leye\n2lf\nl5fr\n4l1g4\nl5ga\nlgar3\nl4ges\nlgo3\n2l3h\nli4ag\nli2am\nliar5iz\nli4as\nli4ato\nli5bi\n5licio\nli4cor\n4lics\n4lict.\nl4icu\nl3icy\nl3ida\nlid5er\n3lidi\nlif3er\nl4iff\nli4fl\n5ligate\n3ligh\nli4gra\n3lik\n4l4i4l\nlim4bl\nlim3i\nli4mo\nl4im4p\nl4ina\n1l4ine\nlin3ea\nlin3i\nlink5er\nli5og\n4l4iq\nlis4p\nl1it\nl2it.\n5litica\nl5i5tics\nliv3er\nl1iz\n4lj\nlka3\nl3kal\nlka4t\nl1l\nl4law\nl2le\nl5lea\nl3lec\nl3leg\nl3lel\nl3le4n\nl3le4t\nll2i\nl2lin4\nl5lina\nll4o\nlloqui5\nll5out\nl5low\n2lm\nl5met\nlm3ing\nl4mod\nlmon4\n2l1n2\n3lo.\nlob5al\nlo4ci\n4lof\n3logic\nl5ogo\n3logu\nlom3er\n5long\nlon4i\nl3o3niz\nlood5\n5lope.\nlop3i\nl3opm\nlora4\nlo4rato\nlo5rie\nlor5ou\n5los.\nlos5et\n5losophiz\n5losophy\nlos4t\nlo4ta\nloun5d\n2lout\n4lov\n2lp\nlpa5b\nl3pha\nl5phi\nlp5ing\nl3pit\nl4pl\nl5pr\n4l1r\n2l1s2\nl4sc\nl2se\nl4sie\n4lt\nlt5ag\nltane5\nl1te\nlten4\nltera4\nlth3i\nl5ties.\nltis4\nl1tr\nltu2\nltur3a\nlu5a\nlu3br\nluch4\nlu3ci\nlu3en\nluf4\nlu5id\nlu4ma\n5lumi\nl5umn.\n5lumnia\nlu3o\nluo3r\n4lup\nluss4\nlus3te\n1lut\nl5ven\nl5vet4\n2l1w\n1ly\n4lya\n4lyb\nly5me\nly3no\n2lys4\nl5yse\n1ma\n2mab\nma2ca\nma5chine\nma4cl\nmag5in\n5magn\n2mah\nmaid5\n4mald\nma3lig\nma5lin\nmal4li\nmal4ty\n5mania\nman5is\nman3iz\n4map\nma5rine.\nma5riz\nmar4ly\nmar3v\nma5sce\nmas4e\nmas1t\n5mate\nmath3\nma3tis\n4matiza\n4m1b\nmba4t5\nm5bil\nm4b3ing\nmbi4v\n4m5c\n4me.\n2med\n4med.\n5media\nme3die\nm5e5dy\nme2g\nmel5on\nmel4t\nme2m\nmem1o3\n1men\nmen4a\nmen5ac\nmen4de\n4mene\nmen4i\nmens4\nmensu5\n3ment\nmen4te\nme5on\nm5ersa\n2mes\n3mesti\nme4ta\nmet3al\nme1te\nme5thi\nm4etr\n5metric\nme5trie\nme3try\nme4v\n4m1f\n2mh\n5mi.\nmi3a\nmid4a\nmid4g\nmig4\n3milia\nm5i5lie\nm4ill\nmin4a\n3mind\nm5inee\nm4ingl\nmin5gli\nm5ingly\nmin4t\nm4inu\nmiot4\nm2is\nmis4er.\nmis5l\nmis4ti\nm5istry\n4mith\nm2iz\n4mk\n4m1l\nm1m\nmma5ry\n4m1n\nmn4a\nm4nin\nmn4o\n1mo\n4mocr\n5mocratiz\nmo2d1\nmo4go\nmois2\nmoi5se\n4mok\nmo5lest\nmo3me\nmon5et\nmon5ge\nmoni3a\nmon4ism\nmon4ist\nmo3niz\nmonol4\nmo3ny.\nmo2r\n4mora.\nmos2\nmo5sey\nmo3sp\nmoth3\nm5ouf\n3mous\nmo2v\n4m1p\nmpara5\nmpa5rab\nmpar5i\nm3pet\nmphas4\nm2pi\nmpi4a\nmp5ies\nm4p1in\nm5pir\nmp5is\nmpo3ri\nmpos5ite\nm4pous\nmpov5\nmp4tr\nm2py\n4m3r\n4m1s2\nm4sh\nm5si\n4mt\n1mu\nmula5r4\n5mult\nmulti3\n3mum\nmun2\n4mup\nmu4u\n4mw\n1na\n2n1a2b\nn4abu\n4nac.\nna4ca\nn5act\nnag5er.\nnak4\nna4li\nna5lia\n4nalt\nna5mit\nn2an\nnanci4\nnan4it\nnank4\nnar3c\n4nare\nnar3i\nnar4l\nn5arm\nn4as\nnas4c\nnas5ti\nn2at\nna3tal\nnato5miz\nn2au\nnau3se\n3naut\nnav4e\n4n1b4\nncar5\nn4ces.\nn3cha\nn5cheo\nn5chil\nn3chis\nnc1in\nnc4it\nncour5a\nn1cr\nn1cu\nn4dai\nn5dan\nn1de\nnd5est.\nndi4b\nn5d2if\nn1dit\nn3diz\nn5duc\nndu4r\nnd2we\n2ne.\nn3ear\nne2b\nneb3u\nne2c\n5neck\n2ned\nne4gat\nneg5ativ\n5nege\nne4la\nnel5iz\nne5mi\nne4mo\n1nen\n4nene\n3neo\nne4po\nne2q\nn1er\nnera5b\nn4erar\nn2ere\nn4er5i\nner4r\n1nes\n2nes.\n4nesp\n2nest\n4nesw\n3netic\nne4v\nn5eve\nne4w\nn3f\nn4gab\nn3gel\nnge4n4e\nn5gere\nn3geri\nng5ha\nn3gib\nng1in\nn5git\nn4gla\nngov4\nng5sh\nn1gu\nn4gum\nn2gy\n4n1h4\nnha4\nnhab3\nnhe4\n3n4ia\nni3an\nni4ap\nni3ba\nni4bl\nni4d\nni5di\nni4er\nni2fi\nni5ficat\nn5igr\nnik4\nn1im\nni3miz\nn1in\n5nine.\nnin4g\nni4o\n5nis.\nnis4ta\nn2it\nn4ith\n3nitio\nn3itor\nni3tr\nn1j\n4nk2\nn5kero\nn3ket\nnk3in\nn1kl\n4n1l\nn5m\nnme4\nnmet4\n4n1n2\nnne4\nnni3al\nnni4v\nnob4l\nno3ble\nn5ocl\n4n3o2d\n3noe\n4nog\nnoge4\nnois5i\nno5l4i\n5nologis\n3nomic\nn5o5miz\nno4mo\nno3my\nno4n\nnon4ag\nnon5i\nn5oniz\n4nop\n5nop5o5li\nnor5ab\nno4rary\n4nosc\nnos4e\nnos5t\nno5ta\n1nou\n3noun\nnov3el3\nnowl3\nn1p4\nnpi4\nnpre4c\nn1q\nn1r\nnru4\n2n1s2\nns5ab\nnsati4\nns4c\nn2se\nn4s3es\nnsid1\nnsig4\nn2sl\nns3m\nn4soc\nns4pe\nn5spi\nnsta5bl\nn1t\nnta4b\nnter3s\nnt2i\nn5tib\nnti4er\nnti2f\nn3tine\nn4t3ing\nnti4p\nntrol5li\nnt4s\nntu3me\nnu1a\nnu4d\nnu5en\nnuf4fe\nn3uin\n3nu3it\nn4um\nnu1me\nn5umi\n3nu4n\nn3uo\nnu3tr\nn1v2\nn1w4\nnym4\nnyp4\n4nz\nn3za\n4oa\noad3\no5a5les\noard3\noas4e\noast5e\noat5i\nob3a3b\no5bar\nobe4l\no1bi\no2bin\nob5ing\no3br\nob3ul\no1ce\noch4\no3chet\nocif3\no4cil\no4clam\no4cod\noc3rac\noc5ratiz\nocre3\n5ocrit\noctor5a\noc3ula\no5cure\nod5ded\nod3ic\nodi3o\no2do4\nodor3\nod5uct.\nod5ucts\no4el\no5eng\no3er\noe4ta\no3ev\no2fi\nof5ite\nofit4t\no2g5a5r\nog5ativ\no4gato\no1ge\no5gene\no5geo\no4ger\no3gie\n1o1gis\nog3it\no4gl\no5g2ly\n3ogniz\no4gro\nogu5i\n1ogy\n2ogyn\no1h2\nohab5\noi2\noic3es\noi3der\noiff4\noig4\noi5let\no3ing\noint5er\no5ism\noi5son\noist5en\noi3ter\no5j\n2ok\no3ken\nok5ie\no1la\no4lan\nolass4\nol2d\nold1e\nol3er\no3lesc\no3let\nol4fi\nol2i\no3lia\no3lice\nol5id.\no3li4f\no5lil\nol3ing\no5lio\no5lis.\nol3ish\no5lite\no5litio\no5liv\nolli4e\nol5ogiz\nolo4r\nol5pl\nol2t\nol3ub\nol3ume\nol3un\no5lus\nol2v\no2ly\nom5ah\noma5l\nom5atiz\nom2be\nom4bl\no2me\nom3ena\nom5erse\no4met\nom5etry\no3mia\nom3ic.\nom3ica\no5mid\nom1in\no5mini\n5ommend\nomo4ge\no4mon\nom3pi\nompro5\no2n\non1a\non4ac\no3nan\non1c\n3oncil\n2ond\non5do\no3nen\non5est\non4gu\non1ic\no3nio\non1is\no5niu\non3key\non4odi\non3omy\non3s\nonspi4\nonspir5a\nonsu4\nonten4\non3t4i\nontif5\non5um\nonva5\noo2\nood5e\nood5i\noo4k\noop3i\no3ord\noost5\no2pa\nope5d\nop1er\n3opera\n4operag\n2oph\no5phan\no5pher\nop3ing\no3pit\no5pon\no4posi\no1pr\nop1u\nopy5\no1q\no1ra\no5ra.\no4r3ag\nor5aliz\nor5ange\nore5a\no5real\nor3ei\nore5sh\nor5est.\norew4\nor4gu\n4o5ria\nor3ica\no5ril\nor1in\no1rio\nor3ity\no3riu\nor2mi\norn2e\no5rof\nor3oug\nor5pe\n3orrh\nor4se\nors5en\norst4\nor3thi\nor3thy\nor4ty\no5rum\no1ry\nos3al\nos2c\nos4ce\no3scop\n4oscopi\no5scr\nos4i4e\nos5itiv\nos3ito\nos3ity\nosi4u\nos4l\no2so\nos4pa\nos4po\nos2ta\no5stati\nos5til\nos5tit\no4tan\notele4g\not3er.\not5ers\no4tes\n4oth\noth5esi\noth3i4\not3ic.\not5ica\no3tice\no3tif\no3tis\noto5s\nou2\nou3bl\nouch5i\nou5et\nou4l\nounc5er\noun2d\nou5v\nov4en\nover4ne\nover3s\nov4ert\no3vis\noviti4\no5v4ol\now3der\now3el\now5est\now1i\nown5i\no4wo\noy1a\n1pa\npa4ca\npa4ce\npac4t\np4ad\n5pagan\np3agat\np4ai\npain4\np4al\npan4a\npan3el\npan4ty\npa3ny\npa1p\npa4pu\npara5bl\npar5age\npar5di\n3pare\npar5el\np4a4ri\npar4is\npa2te\npa5ter\n5pathic\npa5thy\npa4tric\npav4\n3pay\n4p1b\npd4\n4pe.\n3pe4a\npear4l\npe2c\n2p2ed\n3pede\n3pedi\npedia4\nped4ic\np4ee\npee4d\npek4\npe4la\npeli4e\npe4nan\np4enc\npen4th\npe5on\np4era.\npera5bl\np4erag\np4eri\nperi5st\nper4mal\nperme5\np4ern\nper3o\nper3ti\npe5ru\nper1v\npe2t\npe5ten\npe5tiz\n4pf\n4pg\n4ph.\nphar5i\nphe3no\nph4er\nph4es.\nph1ic\n5phie\nph5ing\n5phisti\n3phiz\nph2l\n3phob\n3phone\n5phoni\npho4r\n4phs\nph3t\n5phu\n1phy\npi3a\npian4\npi4cie\npi4cy\np4id\np5ida\npi3de\n5pidi\n3piec\npi3en\npi4grap\npi3lo\npi2n\np4in.\npind4\np4ino\n3pi1o\npion4\np3ith\npi5tha\npi2tu\n2p3k2\n1p2l2\n3plan\nplas5t\npli3a\npli5er\n4plig\npli4n\nploi4\nplu4m\nplum4b\n4p1m\n2p3n\npo4c\n5pod.\npo5em\npo3et5\n5po4g\npoin2\n5point\npoly5t\npo4ni\npo4p\n1p4or\npo4ry\n1pos\npos1s\np4ot\npo4ta\n5poun\n4p1p\nppa5ra\np2pe\np4ped\np5pel\np3pen\np3per\np3pet\nppo5site\npr2\npray4e\n5preci\npre5co\npre3em\npref5ac\npre4la\npre3r\np3rese\n3press\npre5ten\npre3v\n5pri4e\nprin4t3\npri4s\npris3o\np3roca\nprof5it\npro3l\npros3e\npro1t\n2p1s2\np2se\nps4h\np4sib\n2p1t\npt5a4b\np2te\np2th\npti3m\nptu4r\np4tw\npub3\npue4\npuf4\npul3c\npu4m\npu2n\npur4r\n5pus\npu2t\n5pute\nput3er\npu3tr\nput4ted\nput4tin\np3w\nqu2\nqua5v\n2que.\n3quer\n3quet\n2rab\nra3bi\nrach4e\nr5acl\nraf5fi\nraf4t\nr2ai\nra4lo\nram3et\nr2ami\nrane5o\nran4ge\nr4ani\nra5no\nrap3er\n3raphy\nrar5c\nrare4\nrar5ef\n4raril\nr2as\nration4\nrau4t\nra5vai\nrav3el\nra5zie\nr1b\nr4bab\nr4bag\nrbi2\nrbi4f\nr2bin\nr5bine\nrb5ing.\nrb4o\nr1c\nr2ce\nrcen4\nr3cha\nrch4er\nr4ci4b\nrc4it\nrcum3\nr4dal\nrd2i\nrdi4a\nrdi4er\nrdin4\nrd3ing\n2re.\nre1al\nre3an\nre5arr\n5reav\nre4aw\nr5ebrat\nrec5oll\nrec5ompe\nre4cre\n2r2ed\nre1de\nre3dis\nred5it\nre4fac\nre2fe\nre5fer.\nre3fi\nre4fy\nreg3is\nre5it\nre1li\nre5lu\nr4en4ta\nren4te\nre1o\nre5pin\nre4posi\nre1pu\nr1er4\nr4eri\nrero4\nre5ru\nr4es.\nre4spi\nress5ib\nres2t\nre5stal\nre3str\nre4ter\nre4ti4z\nre3tri\nreu2\nre5uti\nrev2\nre4val\nrev3el\nr5ev5er.\nre5vers\nre5vert\nre5vil\nrev5olu\nre4wh\nr1f\nrfu4\nr4fy\nrg2\nrg3er\nr3get\nr3gic\nrgi4n\nrg3ing\nr5gis\nr5git\nr1gl\nrgo4n\nr3gu\nrh4\n4rh.\n4rhal\nri3a\nria4b\nri4ag\nr4ib\nrib3a\nric5as\nr4ice\n4rici\n5ricid\nri4cie\nr4ico\nrid5er\nri3enc\nri3ent\nri1er\nri5et\nrig5an\n5rigi\nril3iz\n5riman\nrim5i\n3rimo\nrim4pe\nr2ina\n5rina.\nrin4d\nrin4e\nrin4g\nri1o\n5riph\nriph5e\nri2pl\nrip5lic\nr4iq\nr2is\nr4is.\nris4c\nr3ish\nris4p\nri3ta3b\nr5ited.\nrit5er.\nrit5ers\nrit3ic\nri2tu\nrit5ur\nriv5el\nriv3et\nriv3i\nr3j\nr3ket\nrk4le\nrk4lin\nr1l\nrle4\nr2led\nr4lig\nr4lis\nrl5ish\nr3lo4\nr1m\nrma5c\nr2me\nr3men\nrm5ers\nrm3ing\nr4ming.\nr4mio\nr3mit\nr4my\nr4nar\nr3nel\nr4ner\nr5net\nr3ney\nr5nic\nr1nis4\nr3nit\nr3niv\nrno4\nr4nou\nr3nu\nrob3l\nr2oc\nro3cr\nro4e\nro1fe\nro5fil\nrok2\nro5ker\n5role.\nrom5ete\nrom4i\nrom4p\nron4al\nron4e\nro5n4is\nron4ta\n1room\n5root\nro3pel\nrop3ic\nror3i\nro5ro\nros5per\nros4s\nro4the\nro4ty\nro4va\nrov5el\nrox5\nr1p\nr4pea\nr5pent\nrp5er.\nr3pet\nrp4h4\nrp3ing\nr3po\nr1r4\nrre4c\nrre4f\nr4reo\nrre4st\nrri4o\nrri4v\nrron4\nrros4\nrrys4\n4rs2\nr1sa\nrsa5ti\nrs4c\nr2se\nr3sec\nrse4cr\nrs5er.\nrs3es\nrse5v2\nr1sh\nr5sha\nr1si\nr4si4b\nrson3\nr1sp\nr5sw\nrtach4\nr4tag\nr3teb\nrten4d\nrte5o\nr1ti\nrt5ib\nrti4d\nr4tier\nr3tig\nrtil3i\nrtil4l\nr4tily\nr4tist\nr4tiv\nr3tri\nrtroph4\nrt4sh\nru3a\nru3e4l\nru3en\nru4gl\nru3in\nrum3pl\nru2n\nrunk5\nrun4ty\nr5usc\nruti5n\nrv4e\nrvel4i\nr3ven\nrv5er.\nr5vest\nr3vey\nr3vic\nrvi4v\nr3vo\nr1w\nry4c\n5rynge\nry3t\nsa2\n2s1ab\n5sack\nsac3ri\ns3act\n5sai\nsalar4\nsal4m\nsa5lo\nsal4t\n3sanc\nsan4de\ns1ap\nsa5ta\n5sa3tio\nsat3u\nsau4\nsa5vor\n5saw\n4s5b\nscan4t5\nsca4p\nscav5\ns4ced\n4scei\ns4ces\nsch2\ns4cho\n3s4cie\n5scin4d\nscle5\ns4cli\nscof4\n4scopy\nscour5a\ns1cu\n4s5d\n4se.\nse4a\nseas4\nsea5w\nse2c3o\n3sect\n4s4ed\nse4d4e\ns5edl\nse2g\nseg3r\n5sei\nse1le\n5self\n5selv\n4seme\nse4mol\nsen5at\n4senc\nsen4d\ns5ened\nsen5g\ns5enin\n4sentd\n4sentl\nsep3a3\n4s1er.\ns4erl\nser4o\n4servo\ns1e4s\nse5sh\nses5t\n5se5um\n5sev\nsev3en\nsew4i\n5sex\n4s3f\n2s3g\ns2h\n2sh.\nsh1er\n5shev\nsh1in\nsh3io\n3ship\nshiv5\nsho4\nsh5old\nshon3\nshor4\nshort5\n4shw\nsi1b\ns5icc\n3side.\n5sides\n5sidi\nsi5diz\n4signa\nsil4e\n4sily\n2s1in\ns2ina\n5sine.\ns3ing\n1sio\n5sion\nsion5a\nsi2r\nsir5a\n1sis\n3sitio\n5siu\n1siv\n5siz\nsk2\n4ske\ns3ket\nsk5ine\nsk5ing\ns1l2\ns3lat\ns2le\nslith5\n2s1m\ns3ma\nsmall3\nsman3\nsmel4\ns5men\n5smith\nsmol5d4\ns1n4\n1so\nso4ce\nsoft3\nso4lab\nsol3d2\nso3lic\n5solv\n3som\n3s4on.\nsona4\nson4g\ns4op\n5sophic\ns5ophiz\ns5ophy\nsor5c\nsor5d\n4sov\nso5vi\n2spa\n5spai\nspa4n\nspen4d\n2s5peo\n2sper\ns2phe\n3spher\nspho5\nspil4\nsp5ing\n4spio\ns4ply\ns4pon\nspor4\n4spot\nsqual4l\ns1r\n2ss\ns1sa\nssas3\ns2s5c\ns3sel\ns5seng\ns4ses.\ns5set\ns1si\ns4sie\nssi4er\nss5ily\ns4sl\nss4li\ns4sn\nsspend4\nss2t\nssur5a\nss5w\n2st.\ns2tag\ns2tal\nstam4i\n5stand\ns4ta4p\n5stat.\ns4ted\nstern5i\ns5tero\nste2w\nstew5a\ns3the\nst2i\ns4ti.\ns5tia\ns1tic\n5stick\ns4tie\ns3tif\nst3ing\n5stir\ns1tle\n5stock\nstom3a\n5stone\ns4top\n3store\nst4r\ns4trad\n5stratu\ns4tray\ns4trid\n4stry\n4st3w\ns2ty\n1su\nsu1al\nsu4b3\nsu2g3\nsu5is\nsuit3\ns4ul\nsu2m\nsum3i\nsu2n\nsu2r\n4sv\nsw2\n4swo\ns4y\n4syc\n3syl\nsyn5o\nsy5rin\n1ta\n3ta.\n2tab\nta5bles\n5taboliz\n4taci\nta5do\n4taf4\ntai5lo\nta2l\nta5la\ntal5en\ntal3i\n4talk\ntal4lis\nta5log\nta5mo\ntan4de\ntanta3\nta5per\nta5pl\ntar4a\n4tarc\n4tare\nta3riz\ntas4e\nta5sy\n4tatic\nta4tur\ntaun4\ntav4\n2taw\ntax4is\n2t1b\n4tc\nt4ch\ntch5et\n4t1d\n4te.\ntead4i\n4teat\ntece4\n5tect\n2t1ed\nte5di\n1tee\nteg4\nte5ger\nte5gi\n3tel.\nteli4\n5tels\nte2ma2\ntem3at\n3tenan\n3tenc\n3tend\n4tenes\n1tent\nten4tag\n1teo\nte4p\nte5pe\nter3c\n5ter3d\n1teri\nter5ies\nter3is\nteri5za\n5ternit\nter5v\n4tes.\n4tess\nt3ess.\nteth5e\n3teu\n3tex\n4tey\n2t1f\n4t1g\n2th.\nthan4\nth2e\n4thea\nth3eas\nthe5at\nthe3is\n3thet\nth5ic.\nth5ica\n4thil\n5think\n4thl\nth5ode\n5thodic\n4thoo\nthor5it\ntho5riz\n2ths\n1tia\nti4ab\nti4ato\n2ti2b\n4tick\nt4ico\nt4ic1u\n5tidi\n3tien\ntif2\nti5fy\n2tig\n5tigu\ntill5in\n1tim\n4timp\ntim5ul\n2t1in\nt2ina\n3tine.\n3tini\n1tio\nti5oc\ntion5ee\n5tiq\nti3sa\n3tise\ntis4m\nti5so\ntis4p\n5tistica\nti3tl\nti4u\n1tiv\ntiv4a\n1tiz\nti3za\nti3zen\n2tl\nt5la\ntlan4\n3tle.\n3tled\n3tles.\nt5let.\nt5lo\n4t1m\ntme4\n2t1n2\n1to\nto3b\nto5crat\n4todo\n2tof\nto2gr\nto5ic\nto2ma\ntom4b\nto3my\nton4ali\nto3nat\n4tono\n4tony\nto2ra\nto3rie\ntor5iz\ntos2\n5tour\n4tout\nto3war\n4t1p\n1tra\ntra3b\ntra5ch\ntraci4\ntrac4it\ntrac4te\ntras4\ntra5ven\ntrav5es5\ntre5f\ntre4m\ntrem5i\n5tria\ntri5ces\n5tricia\n4trics\n2trim\ntri4v\ntro5mi\ntron5i\n4trony\ntro5phe\ntro3sp\ntro3v\ntru5i\ntrus4\n4t1s2\nt4sc\ntsh4\nt4sw\n4t3t2\nt4tes\nt5to\nttu4\n1tu\ntu1a\ntu3ar\ntu4bi\ntud2\n4tue\n4tuf4\n5tu3i\n3tum\ntu4nis\n2t3up.\n3ture\n5turi\ntur3is\ntur5o\ntu5ry\n3tus\n4tv\ntw4\n4t1wa\ntwis4\n4two\n1ty\n4tya\n2tyl\ntype3\nty5ph\n4tz\ntz4e\n4uab\nuac4\nua5na\nuan4i\nuar5ant\nuar2d\nuar3i\nuar3t\nu1at\nuav4\nub4e\nu4bel\nu3ber\nu4bero\nu1b4i\nu4b5ing\nu3ble.\nu3ca\nuci4b\nuc4it\nucle3\nu3cr\nu3cu\nu4cy\nud5d\nud3er\nud5est\nudev4\nu1dic\nud3ied\nud3ies\nud5is\nu5dit\nu4don\nud4si\nu4du\nu4ene\nuens4\nuen4te\nuer4il\n3ufa\nu3fl\nugh3en\nug5in\n2ui2\nuil5iz\nui4n\nu1ing\nuir4m\nuita4\nuiv3\nuiv4er.\nu5j\n4uk\nu1la\nula5b\nu5lati\nulch4\n5ulche\nul3der\nul4e\nu1len\nul4gi\nul2i\nu5lia\nul3ing\nul5ish\nul4lar\nul4li4b\nul4lis\n4ul3m\nu1l4o\n4uls\nuls5es\nul1ti\nultra3\n4ultu\nu3lu\nul5ul\nul5v\num5ab\num4bi\num4bly\nu1mi\nu4m3ing\numor5o\num2p\nunat4\nu2ne\nun4er\nu1ni\nun4im\nu2nin\nun5ish\nuni3v\nun3s4\nun4sw\nunt3ab\nun4ter.\nun4tes\nunu4\nun5y\nun5z\nu4ors\nu5os\nu1ou\nu1pe\nuper5s\nu5pia\nup3ing\nu3pl\nup3p\nupport5\nupt5ib\nuptu4\nu1ra\n4ura.\nu4rag\nu4ras\nur4be\nurc4\nur1d\nure5at\nur4fer\nur4fr\nu3rif\nuri4fic\nur1in\nu3rio\nu1rit\nur3iz\nur2l\nurl5ing.\nur4no\nuros4\nur4pe\nur4pi\nurs5er\nur5tes\nur3the\nurti4\nur4tie\nu3ru\n2us\nu5sad\nu5san\nus4ap\nusc2\nus3ci\nuse5a\nu5sia\nu3sic\nus4lin\nus1p\nus5sl\nus5tere\nus1tr\nu2su\nusur4\nuta4b\nu3tat\n4ute.\n4utel\n4uten\nuten4i\n4u1t2i\nuti5liz\nu3tine\nut3ing\nution5a\nu4tis\n5u5tiz\nu4t1l\nut5of\nuto5g\nuto5matic\nu5ton\nu4tou\nuts4\nu3u\nuu4m\nu1v2\nuxu3\nuz4e\n1va\n5va.\n2v1a4b\nvac5il\nvac3u\nvag4\nva4ge\nva5lie\nval5o\nval1u\nva5mo\nva5niz\nva5pi\nvar5ied\n3vat\n4ve.\n4ved\nveg3\nv3el.\nvel3li\nve4lo\nv4ely\nven3om\nv5enue\nv4erd\n5vere.\nv4erel\nv3eren\nver5enc\nv4eres\nver3ie\nvermi4n\n3verse\nver3th\nv4e2s\n4ves.\nves4te\nve4te\nvet3er\nve4ty\nvi5ali\n5vian\n5vide.\n5vided\n4v3iden\n5vides\n5vidi\nv3if\nvi5gn\nvik4\n2vil\n5vilit\nv3i3liz\nv1in\n4vi4na\nv2inc\nvin5d\n4ving\nvio3l\nv3io4r\nvi1ou\nvi4p\nvi5ro\nvis3it\nvi3so\nvi3su\n4viti\nvit3r\n4vity\n3viv\n5vo.\nvoi4\n3vok\nvo4la\nv5ole\n5volt\n3volv\nvom5i\nvor5ab\nvori4\nvo4ry\nvo4ta\n4votee\n4vv4\nv4y\nw5abl\n2wac\nwa5ger\nwag5o\nwait5\nw5al.\nwam4\nwar4t\nwas4t\nwa1te\nwa5ver\nw1b\nwea5rie\nweath3\nwed4n\nweet3\nwee5v\nwel4l\nw1er\nwest3\nw3ev\nwhi4\nwi2\nwil2\nwill5in\nwin4de\nwin4g\nwir4\n3wise\nwith3\nwiz5\nw4k\nwl4es\nwl3in\nw4no\n1wo2\nwom1\nwo5ven\nw5p\nwra4\nwri4\nwrita4\nw3sh\nws4l\nws4pe\nw5s4t\n4wt\nwy4\nx1a\nxac5e\nx4ago\nxam3\nx4ap\nxas5\nx3c2\nx1e\nxe4cuto\nx2ed\nxer4i\nxe5ro\nx1h\nxhi2\nxhil5\nxhu4\nx3i\nxi5a\nxi5c\nxi5di\nx4ime\nxi5miz\nx3o\nx4ob\nx3p\nxpan4d\nxpecto5\nxpe3d\nx1t2\nx3ti\nx1u\nxu3a\nxx4\ny5ac\n3yar4\ny5at\ny1b\ny1c\ny2ce\nyc5er\ny3ch\nych4e\nycom4\nycot4\ny1d\ny5ee\ny1er\ny4erf\nyes4\nye4t\ny5gi\n4y3h\ny1i\ny3la\nylla5bl\ny3lo\ny5lu\nymbol5\nyme4\nympa3\nyn3chr\nyn5d\nyn5g\nyn5ic\n5ynx\ny1o4\nyo5d\ny4o5g\nyom4\nyo5net\ny4ons\ny4os\ny4ped\nyper5\nyp3i\ny3po\ny4poc\nyp2ta\ny5pu\nyra5m\nyr5ia\ny3ro\nyr4r\nys4c\ny3s2e\nys3ica\nys3io\n3ysis\ny4so\nyss4\nys1t\nys3ta\nysur4\ny3thin\nyt3ic\ny1w\nza1\nz5a2b\nzar2\n4zb\n2ze\nze4n\nze4p\nz1er\nze3ro\nzet4\n2z1i\nz4il\nz4is\n5zl\n4zm\n1zo\nzo4m\nzo5ol\nzte4\n4z1z2\nz4zy\n.con5gr\n.de5riva\n.dri5v4\n.eth1y6l1\n.eu4ler\n.ev2\n.ever5si5b\n.ga4s1om1\n.ge4ome\n.ge5ot1\n.he3mo1\n.he3p6a\n.he3roe\n.in5u2t\n.kil2n3i\n.ko6r1te1\n.le6ices\n.me4ga1l\n.met4ala\n.mim5i2c1\n.mi1s4ers\n.ne6o3f\n.noe1th\n.non1e2m\n.poly1s\n.post1am\n.pre1am\n.rav5en1o\n.semi5\n.sem4ic\n.semid6\n.semip4\n.semir4\n.sem6is4\n.semiv4\n.sph6in1\n.spin1o\n.ta5pes1tr\n.te3legr\n.to6pog\n.to2q\n.un3at5t\n.un5err5\n.vi2c3ar\n.we2b1l\n.re1e4c\na5bolic\na2cabl\naf6fish\nam1en3ta5b\nanal6ys\nano5a2c\nans5gr\nans3v\nanti1d\nan3ti1n2\nanti1re\na4pe5able\nar3che5t\nar2range\nas5ymptot\nath3er1o1s\nat6tes.\naugh4tl\nau5li5f\nav3iou\nback2er.\nba6r1onie\nba1thy\nbbi4t\nbe2vie\nbi5d2if\nbil2lab\nbio5m\nbi1orb\nbio1rh\nb1i3tive\nblan2d1\nblin2d1\nblon2d2\nbor1no5\nbo2t1u1l\nbrus4q\nbus6i2er\nbus6i2es\nbuss4ing\nbut2ed.\nbut4ted\ncad5e1m\ncat1a1s2\n4chs.\nchs3hu\nchie5vo\ncig3a3r\ncin2q\ncle4ar\nco6ph1o3n\ncous2ti\ncri3tie\ncroc1o1d\ncro5e2co\nc2tro3me6c\n1cu2r1ance\n2d3alone\ndata1b\ndd5a5b\nd2d5ib\nde4als.\nde5clar1\nde2c5lina\nde3fin3iti\nde2mos\ndes3ic\nde2tic\ndic1aid\ndif5fra\n3di1methy\ndi2ren\ndi2rer\n2d1lead\n2d1li2e\n3do5word\ndren1a5l\ndrif2t1a\nd1ri3pleg5\ndrom3e5d\nd3tab\ndu2al.\ndu1op1o1l\nea4n3ies\ne3chas\nedg1l\ned1uling\neli2t1is\ne1loa\nen1dix\neo3grap\n1e6p3i3neph1\ne2r3i4an.\ne3spac6i\neth1y6l1ene\n5eu2clid1\nfeb1rua\nfermi1o\n3fich\nfit5ted.\nfla1g6el\nflow2er.\n3fluor\ngen2cy.\nge3o1d\nght1we\ng1lead\nget2ic.\n4g1lish\n5glo5bin\n1g2nac\ngnet1ism\ngno5mo\ng2n1or.\ng2noresp\n2g1o4n3i1za\ngraph5er.\ngriev1\ng1utan\nhair1s\nha2p3ar5r\nhatch1\nhex2a3\nhite3sid\nh3i5pel1a4\nhnau3z\nho6r1ic.\nh2t1eou\nhypo1tha\nid4ios\nifac1et\nign4it\nignit1er\ni4jk\nim3ped3a\ninfra1s2\ni5nitely.\nirre6v3oc\ni1tesima\nith5i2l\nitin5er5ar\njanu3a\njapan1e2s\nje1re1m\n1ke6ling\n1ki5netic\n1kovian\nk3sha\nla4c3i5e\nlai6n3ess\nlar5ce1n\nl3chai\nl3chil6d1\nlead6er.\nlea4s1a\n1lec3ta6b\nle3g6en2dre\n1le1noid\nlith1o5g\nll1fl\nl2l3ish\nl5mo3nell\nlo1bot1o1\nlo2ges.\nload4ed.\nload6er.\nl3tea\nlth5i2ly\nlue1p\n1lunk3er\n1lum5bia.\n3lyg1a1mi\nly5styr\nma1la1p\nm2an.\nman3u1sc\nmar1gin1\nmedi2c\nmed3i3cin\nmedio6c1\nme3gran3\nm2en.\n3mi3da5b\n3milita\nmil2l1ag\nmil5li5li\nmi6n3is.\nmi1n2ut1er\nmi1n2ut1est\nm3ma1b\n5maph1ro1\n5moc1ra1t\nmo5e2las\nmol1e5c\nmon4ey1l\nmono3ch\nmo4no1en\nmoro6n5is\nmono1s6\nmoth4et2\nm1ou3sin\nm5shack2\nmu2dro\nmul2ti5u\nn3ar4chs.\nn3ch2es1t\nne3back\n2ne1ski\nn1dieck\nnd3thr\nnfi6n3ites\n4n5i4an.\nnge5nes\nng1ho\nng1spr\nnk3rup\nn5less\n5noc3er1os\nnom1a6l\nnom5e1no\nn1o1mist\nnon1eq\nnon1i4so\n5nop1oly.\nno1vemb\nns5ceiv\nns4moo\nntre1p\nobli2g1\no3chas\nodel3li\nodit1ic\noerst2\noke1st\no3les3ter\noli3gop1o1\no1lo3n4om\no3mecha6\nonom1ic\no3norma\no3no2t1o3n\no3nou\nop1ism.\nor4tho3ni4t\north1ri\nor5tively\no4s3pher\no5test1er\no5tes3tor\noth3e1o1s\nou3ba3do\no6v3i4an.\noxi6d1ic\npal6mat\nparag6ra4\npar4a1le\nparam4\npara3me\npee2v1\nphi2l3ant\nphi5lat1e3l\npi2c1a3d\npli2c1ab\npli5nar\npoin3ca\n1pole.\npoly1e\npo3lyph1ono\n1prema3c\npre1neu\npres2pli\npro2cess\nproc3i3ty.\npro2g1e\n3pseu2d\npseu3d6o3d2\npseu3d6o3f2\npto3mat4\np5trol3\npu5bes5c\nquain2t1e\nqu6a3si3\nquasir6\nquasis6\nquin5tes5s\nqui3v4ar\nr1abolic\n3rab1o1loi\nra3chu\nr3a3dig\nradi1o6g\nr2amen\n3ra4m5e1triz\nra3mou\nra5n2has\nra1or\nr3bin1ge\nre2c3i1pr\nrec5t6ang\nre4t1ribu\nr3ial.\nriv1o1l\n6rk.\nrk1ho\nr1krau\n6rks.\nr5le5qu\nro1bot1\nro5e2las\nro5epide1\nro3mesh\nro1tron\nr3pau5li\nrse1rad1i\nr1thou\nr1treu\nr1veil\nrz1sc\nsales3c\nsales5w\n5sa3par5il\nsca6p1er\nsca2t1ol\ns4chitz\nschro1ding1\n1sci2utt\nscrap4er.\nscy4th1\nsem1a1ph\nse3mes1t\nse1mi6t5ic\nsep3temb\nshoe1st\nsid2ed.\nside5st\nside5sw\nsi5resid\nsky1sc\n3slova1kia\n3s2og1a1my\nso2lute\n3s2pace\n1s2pacin\nspe3cio\nspher1o\nspi2c1il\nspokes5w\nsports3c\nsports3w\ns3qui3to\ns2s1a3chu1\nss3hat\ns2s3i4an.\ns5sign5a3b\n1s2tamp\ns2t1ant5shi\nstar3tli\nsta1ti\nst5b\n1stor1ab\nstrat1a1g\nstrib5ut\nst5scr\nstu1pi4d1\nstyl1is\nsu2per1e6\n1sync\n1syth3i2\nswimm6\n5tab1o1lism\nta3gon.\ntalk1a5\nt1a1min\nt6ap6ath\n5tar2rh\ntch1c\ntch3i1er\nt1cr\nteach4er.\ntele2g\ntele1r6o\n3ter1gei\nter2ic.\nt3ess2es\ntha4l1am\ntho3don\nth1o5gen1i\ntho1k2er\nthy4l1an\nthy3sc\n2t3i4an.\nti2n3o1m\nt1li2er\ntolo2gy\ntot3ic\ntrai3tor1\ntra1vers\ntravers3a3b\ntreach1e\ntr4ial.\n3tro1le1um\ntrof4ic.\ntro3fit\ntro1p2is\n3trop1o5les\n3trop1o5lis\nt1ro1pol3it\ntsch3ie\nttrib1ut1\nturn3ar\nt1wh\nty2p5al\nua3drati\nuad1ratu\nu5do3ny\nuea1m\nu2r1al.\nuri4al.\nus2er.\nv1ativ\nv1oir5du1\nva6guer\nvaude3v\n1verely.\nv1er1eig\nves1tite\nvi1vip3a3r\nvoice1p\nwaste3w6a2\nwave1g4\nw3c\nweek1n\nwide5sp\nwo4k1en\nwrap3aro\nwrit6er.\nx1q\nxquis3\ny5che3d\nym5e5try\ny1stro\nyes5ter1y\nz3ian.\nz3o1phr\nz2z3w\n"
  },
  {
    "path": "test/hyphenate-nbsp.html",
    "content": "<div style=\"font-size:10em\" lang=de>\nWei kurz&nbsp;und&nbsp;knapp www.dillo.org\n</div>\n"
  },
  {
    "path": "test/hyphens-etc.html",
    "content": "<p>Abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde&shy;abcde</p>\n<p>Abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde-abcde</p>\n<p>Abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde&#x2010;abcde</p>\n<p>Abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde&mdash;abcde</p>\n<p lang=\"de\">Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen&mdash;Nordrhein-Westfalen</p>\n<p lang=\"de\">Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen&mdash;Nord&shy;rheinwestfalen</p>\n"
  },
  {
    "path": "test/identity.cc",
    "content": "/*\n * This small program tests how IdentifiableObject works with multiple\n * inheritance (\"diamond\" inheritance, more precisely, since all\n * classes have there root in IdentifiableObject.)\n *\n * Current status: With virtual superclasses, you get a class\n * hierarchie \"root -> A -> B -> C\", so that the first part of this\n * example works actually (C is a subclass of A and of B), but the\n * second fails (it should print \"false\", but it is erroneously\n * assumed that B is a subclass of A.)\n */\n\n#include \"../lout/identity.hh\"\n\nusing namespace lout::identity;\n\nclass A: virtual public IdentifiableObject\n{\npublic:\n   static int CLASS_ID;\n   inline A () { registerName (\"A\", &CLASS_ID); }\n};\n\nclass B: virtual public IdentifiableObject\n{\npublic:\n   static int CLASS_ID;\n   inline B () { registerName (\"B\", &CLASS_ID); }\n};\n\nclass C: public A, public B\n{\npublic:\n   static int CLASS_ID;\n   inline C () { registerName (\"C\", &CLASS_ID); }\n};\n\nint A::CLASS_ID = -1, B::CLASS_ID = -1, C::CLASS_ID = -1;\n\nint main (int argc, char *argv[])\n{\n   printf (\"A: %d, B: %d, C: %d\\n\", A::CLASS_ID, B::CLASS_ID, C::CLASS_ID);\n\n   C x;\n   assert (x.instanceOf (A::CLASS_ID));\n   assert (x.instanceOf (B::CLASS_ID));\n   assert (x.instanceOf (C::CLASS_ID));\n   printf (\"x: %d\\n\", x.getClassId ());\n\n   B y;\n   printf (\"y: %d; instance of A: %s\\n\",\n           y.getClassId (), y.instanceOf (B::CLASS_ID) ? \"true\" : \"false\");\n\n   return 0;\n}\n"
  },
  {
    "path": "test/lang.html",
    "content": "<p>Bla bla bla bla bla bla bla bla bla Bla bla bla bla bla\nbla bla bla bla bla:\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung</p>\n<p lang=\"de\">Bla bla bla bla bla bla bla bla bla Bla bla bla bla bla\nbla bla bla bla bla:\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung</p>\n<p lang=\"en\">Bla bla bla bla bla bla bla bla bla Bla bla bla bla bla\nbla bla bla bla bla:\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung</p>\n<p lang=\"es\">Bla bla bla bla bla bla bla bla bla Bla bla bla bla bla\nbla bla bla bla bla:\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung</p>\n\n<hr />\n\n<p>Bla bla bla bla bla bla bla bla bla Bla bla bla bla bla\nbla bla bla bla bla:\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung</p>\n<p xml:lang=\"de\">Bla bla bla bla bla bla bla bla bla Bla bla bla bla bla\nbla bla bla bla bla:\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung</p>\n<p xml:lang=\"en\">Bla bla bla bla bla bla bla bla bla Bla bla bla bla bla\nbla bla bla bla bla:\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung</p>\n<p xml:lang=\"es\">Bla bla bla bla bla bla bla bla bla Bla bla bla bla bla\nbla bla bla bla bla:\nGrundstücksverkehrsgenehmigungszuständigkeitsübertragungsverordnung</p>\n"
  },
  {
    "path": "test/liang.cc",
    "content": "#include <unistd.h>\n\n#include \"../dw/fltkcore.hh\"\n#include \"../dw/hyphenator.hh\"\n\nvoid hyphenateWord (dw::core::Platform *p, const char *lang, const char *word)\n{\n   dw::Hyphenator *h = dw::Hyphenator::getHyphenator (lang);\n\n   int numBreaks;\n   int *breakPos = h->hyphenateWord (p, word, &numBreaks);\n   for (int i = 0; i < numBreaks + 1; i++) {\n      if (i != 0)\n         printf (\" \\xc2\\xad \");\n      int start = (i == 0 ? 0 : breakPos[i - 1]);\n      int end = (i == numBreaks ? strlen (word) : breakPos[i]);\n      for (int j = start; j < end; j++)\n         putchar (word[j]);\n   }\n   putchar ('\\n');\n   if (breakPos)\n      free (breakPos);\n}\n\nint main (int argc, char *argv[])\n{\n   dw::fltk::FltkPlatform p;\n\n   if (argc > 1) {\n      // Usage: liang [-l LANG] WORD ...\n      \n      const char *lang = \"de\";\n      char opt;\n      \n      while ((opt = getopt(argc, argv, \"l:\")) != -1) {\n         switch (opt) {\n         case 'l':\n            lang = optarg;\n            break;            \n         }\n      }\n\n      for (int i = optind; i < argc; i++) \n         hyphenateWord (&p, lang, argv[i]);\n\n   } else {\n      hyphenateWord (&p, \"de\", \"...\");\n      hyphenateWord (&p, \"de\", \"Jahrhundertroman\");\n      hyphenateWord (&p, \"de\", \"JAHRHUNDERTROMAN\");\n      hyphenateWord (&p, \"de\", \"„Jahrhundertroman“\");\n      hyphenateWord (&p, \"de\", \"währenddessen\");\n      hyphenateWord (&p, \"de\", \"„währenddessen“\");\n      hyphenateWord (&p, \"de\", \"Ückendorf\");\n      hyphenateWord (&p, \"de\", \"über\");\n      hyphenateWord (&p, \"de\", \"aber\");\n      hyphenateWord (&p, \"de\", \"Ackermann\");\n      hyphenateWord (&p, \"de\", \"„Ackermann“\");\n      hyphenateWord (&p, \"de\", \"entscheidet.\");\n      hyphenateWord (&p, \"de\", \"Grundstücksverkehrsgenehmigungszuständigkeits\"\n                     \"übertragungsverordnung\");\n      hyphenateWord (&p, \"de\", \"„Grundstücksverkehrsgenehmigungszuständigkeits\"\n                     \"übertragungsverordnung“\");\n      hyphenateWord (&p, \"de\", \"Grundstücksverkehrsgenehmigungszuständigkeit\");\n      hyphenateWord (&p, \"de\",\n                     \"„Grundstücksverkehrsgenehmigungszuständigkeit“\");\n      hyphenateWord (&p, \"de\",\n                     \"(6R,7R)-7-[2-(2-Amino-4-thiazolyl)-glyoxylamido]-3-\"\n                     \"(2,5-dihydro-6-hydroxy-2-methyl-5-oxo-1,2,4-triazin-3-yl-\"\n                     \"thiomethyl)-8-oxo-5-thia-1-azabicyclo[4.2.0]oct-2-en-2-\"\n                     \"carbonsäure-7²-(Z)-(O-methyloxim)\");\n      hyphenateWord (&p, \"de\", \"Abtei-Stadt\");\n      hyphenateWord (&p, \"de\", \"Nordrhein-Westfalen\");\n      hyphenateWord (&p, \"de\", \"kurz\\xc2\\xa0und\\xc2\\xa0knapp\");\n      hyphenateWord (&p, \"de\", \"weiß\");\n      hyphenateWord (&p, \"de\", \"www.dillo.org\");\n   }\n\n   return 0;\n}\n"
  },
  {
    "path": "test/notsosimplevector.cc",
    "content": "#include \"../lout/misc.hh\"\n\nstatic void print (lout::misc::NotSoSimpleVector<int> *v)\n{\n   for (int i = 0; i < v->size(); i++) {\n      // Uncomment for debugging, after making the respective members public.\n      //if (v->startExtra != -1 && i == v->startExtra + v->numExtra)\n      //   printf (\" ]\");\n      if (i > 0)\n         printf (\", \");\n      //if (i == v->startExtra)\n      //   printf (\"[ \");\n\n      printf (\"%d\", v->get(i));\n   }\n\n   printf (\" (%d elements)\\n\", v->size ());\n}\n\nint main (int argc, char *argv[])\n{\n   lout::misc::NotSoSimpleVector<int> v(1);\n\n   for (int i = 1; i <= 10; i++) {\n      v.increase ();\n      v.set(v.size () - 1, i);\n   }\n\n   print (&v);\n\n   v.insert (2, 4);\n   for (int i = 0; i < 5; i++)\n      v.set (2 + i, 31 + i);\n\n   print (&v);\n\n   v.insert (8, 4);\n   for (int i = 0; i < 5; i++)\n      v.set (8 + i, 51 + i);\n\n   print (&v);\n\n   v.insert (10, 4);\n   for (int i = 0; i < 5; i++)\n      v.set (10 + i, 531 + i);\n\n   print (&v);\n\n   v.insert (1, 4);\n   for (int i = 0; i < 5; i++)\n      v.set (1 + i, 21 + i);\n\n   print (&v);\n\n   int n = v.size ();\n   v.insert (n, 5);\n   for (int i = 0; i < 5; i++)\n      v.set (n + i, 101 + i);\n\n   print (&v);\n\n   return 0;\n}\n"
  },
  {
    "path": "test/shapes.cc",
    "content": "/*\n * Dillo Widget\n *\n * Copyright 2005-2007 Sebastian Geerken <sgeerken@dillo.org>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\n\n\n#include \"../dw/core.hh\"\n\nusing namespace dw::core;\nusing namespace lout::misc;\n\nint main()\n{\n   Polygon poly;\n   poly.addPoint (50, 10);\n   poly.addPoint (90, 90);\n   poly.addPoint (10, 90);\n\n   printf(\"first test\\n\");\n   assert (poly.isPointWithin (50, 50));\n   printf(\"second test\\n\");\n   assert (!poly.isPointWithin (10, 10));\n   printf(\"third test\\n\");\n   assert (!poly.isPointWithin (90, 50));\n}\n"
  },
  {
    "path": "test/table-1.html",
    "content": "<table>\n<tr>\n<td>Short, <span style=\"white-space: nowrap\">then some more text,\nwhich must not be broken.</span>\n<td>This is a rather long text to increase the maximal paragraph\nwidth. Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\naccusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae\nab illo inventore veritatis et quasi architecto beatae vitae dicta\nsunt, explicabo. nemo enim ipsam voluptatem, quia voluptas sit,\naspernatur aut odit aut fugit, sed quia consequuntur magni dolores\neos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,\nqui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit,\nsed quia non numquam eius modi tempora incidunt, ut labore et dolore\nmagnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis\nnostrum exercitationem ullam corporis suscipit laboriosam, nisi ut\naliquid ex ea commodi consequatur? quis autem vel eum iure\nreprehenderit, qui in ea voluptate velit esse, quam nihil molestiae\nconsequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla\npariatur?\n</tr>\n</table>\n"
  },
  {
    "path": "test/table-h1.html",
    "content": "<table>\n<tr>\n<td lang=\"de\">Grundstücksverkehrsgenehmigungszuständigkeit ABC\n<td>Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\naccusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae\nab illo inventore veritatis et quasi architecto beatae vitae dicta\nsunt, explicabo. nemo enim ipsam voluptatem, quia voluptas sit,\naspernatur aut odit aut fugit, sed quia consequuntur magni dolores\neos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,\nqui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit,\nsed quia non numquam eius modi tempora incidunt, ut labore et dolore\nmagnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis\nnostrum exercitationem ullam corporis suscipit laboriosam, nisi ut\naliquid ex ea commodi consequatur? quis autem vel eum iure\nreprehenderit, qui in ea voluptate velit esse, quam nihil molestiae\nconsequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla\npariatur?\n</table>\n\n<table>\n<tr>\n<td>Grund&shy;stücks&shy;ver&shy;kehrs&shy;ge&shy;neh&shy;mi&shy;gungs&shy;zu&shy;stän&shy;dig&shy;keit ABC\n<td>Sed ut perspiciatis, unde omnis iste natus error sit voluptatem\naccusantium doloremque laudantium, totam rem aperiam eaque ipsa, quae\nab illo inventore veritatis et quasi architecto beatae vitae dicta\nsunt, explicabo. nemo enim ipsam voluptatem, quia voluptas sit,\naspernatur aut odit aut fugit, sed quia consequuntur magni dolores\neos, qui ratione voluptatem sequi nesciunt, neque porro quisquam est,\nqui dolorem ipsum, quia dolor sit, amet, consectetur, adipisci velit,\nsed quia non numquam eius modi tempora incidunt, ut labore et dolore\nmagnam aliquam quaerat voluptatem. ut enim ad minima veniam, quis\nnostrum exercitationem ullam corporis suscipit laboriosam, nisi ut\naliquid ex ea commodi consequatur? quis autem vel eum iure\nreprehenderit, qui in ea voluptate velit esse, quam nihil molestiae\nconsequatur, vel illum, qui dolorem eum fugiat, quo voluptas nulla\npariatur?\n</table>\n"
  },
  {
    "path": "test/table-narrow.html",
    "content": "<table border=1>\n<tr>\n<td>W d<whatever>aaaaaaaaaaaa*<br>\n<td>WW d<whatever>aaaaaaaaaaaa*<br>\n<td>WWW d<whatever>aaaaaaaaaaaa*<br>\n<td>WWWW d<whatever>aaaaaaaaaaaa*<br>\n<td>WWWWW d<whatever>aaaaaaaaaaaa*<br>\n<td>WWWWWW d<whatever>aaaaaaaaaaaa*<br>\n</table>\n"
  },
  {
    "path": "test/table-thead-tfoot.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Test thead, tbody and tfoot</title>\n    <style>\n      thead {color: green;}\n      tbody {color: blue;}\n      tfoot {color: red;}\n\n      table, th, td {\n        border: 1px solid black;\n      }\n    </style>\n  </head>\n  <body>\n    <table>\n      <thead>\n        <tr>\n          <th>Month</th>\n          <th>Savings</th>\n        </tr>\n      </thead>\n      <tbody>\n        <tr>\n          <td>January</td>\n          <td>$100</td>\n        </tr>\n        <tr>\n          <td>February</td>\n          <td>$80</td>\n        </tr>\n      </tbody>\n      <tfoot>\n        <tr>\n          <td>Sum</td>\n          <td>$180</td>\n        </tr>\n      </tfoot>\n    </table> \n  </body>\n</html>\n"
  },
  {
    "path": "test/trie.cc",
    "content": "#include \"../dw/hyphenator.hh\"\n\nint main (int argc, char *argv[])\n{\n   if (argc < 2) {\n      fprintf(stderr, \"Usage: trie <pattern file>\\n\");\n      exit (1);\n   }\n\n   /* Use pack = 1024 to create a really small trie - can take a while.\n    */\n   dw::Hyphenator hyphenator (argv[1], NULL, 1024);\n   hyphenator.saveTrie (stdout);\n}\n"
  },
  {
    "path": "test/unicode_test.cc",
    "content": "#include <string.h>\n#include <stdio.h>\n#include <FL/fl_utf8.h>\n#include \"../lout/unicode.hh\"\n\nusing namespace lout::unicode;\n\nint main (int argc, char *argv[])\n{\n   // 0-terminated string\n   const char *t1 = \"abcäöüабв−‐\";\n\n   // not 0-terminated; copy from 0-terminated\n   int t2len = strlen (t1);\n   char t2[t2len];\n   for (int i = 0; i < t2len; i++)\n      t2[i] = t1[i];\n\n   puts (\"===== misc::unicode, 0-terminated =====\");\n   for (const char *s = t1; s; s = nextUtf8Char (s))\n      printf (\"%3d -> U+%04x ('%s')\\n\", (int)(s - t1), decodeUtf8(s), s);\n\n   puts (\"===== Fltk, 0-terminated =====\");\n   for (const char *s = t1; *s; s = fl_utf8fwd (s + 1, t1, t1 + strlen (t1)))\n      printf (\"%3d -> U+%04x ('%s')\\n\", (int)(s - t1), decodeUtf8(s), s);\n\n   puts (\"===== misc::unicode, not 0-terminated =====\");\n   for (const char *s = t2; s; s = nextUtf8Char (s, t2len - (s - t2)))\n      printf (\"%3d -> U+%04x\\n\", (int)(s - t2),\n              decodeUtf8(s, t2len - (s - t2)));\n\n   puts (\"===== Fltk, not 0-terminated =====\");\n   for (const char *s = t2; *s;\n        s - t2 < t2len && (s = fl_utf8fwd (s + 1, t2, t2 + t2len)))\n      printf (\"%3d -> U+%04x\\n\", (int)(s - t2),\n              decodeUtf8(s, t2len - (s - t2)));\n\n   return 0;\n}\n"
  },
  {
    "path": "test/white-space.html",
    "content": "<h1>nowrap</h1>\n<div style=\"white-space: nowrap\">\nhallo   dillo hallo   dillo hallo   dillo hallo   dillo\nhallo   dillo hallo   dillo hallo   dillo hallo   dillo\n</div>\n\n<h1>pre</h1>\n\n<div style=\"white-space: pre\">\nhallo   dillo hallo   dillo hallo   dillo hallo   dillo\nhallo   dillo hallo   dillo hallo   dillo hallo   dillo\n</div>\n\n<h1>pre-wrap</h1>\n<div style=\"white-space: pre-wrap\">\nhallo   dillo hallo   dillo hallo   dillo hallo   dillo\nhallo   dillo hallo   dillo hallo   dillo hallo   dillo\n</div>\n\n<h1>pre-line</h1>\n<div style=\"white-space: pre-line\">\nhallo   dillo hallo   dillo hallo   dillo hallo   dillo\nhallo   dillo hallo   dillo hallo   dillo hallo   dillo\n</div>\n"
  },
  {
    "path": "test_files/page.gmi",
    "content": "# Title of the page\nA paragraph of text, with a normal length line...\nSome empty lines to preserve:\n\n\nAnother paragraph with a very log line (should be wrapped): I originally designed this foundry with the intent of building a Gingery-style lathe. Over time, I became less interested in the idea, although it still occasionally crosses my mind. Regardless, the ability to melt and cast scrap aluminum has been an indispensible part of my workshop ever since. In essence, the foundry is just a hole in the ground filled with charcoal, with two accessories. First, bricks to line the walls of the hole. These stop the dirt, which dries when heated, from collapsing inward. Second, a steel tube through which air can be blown, to increase the temperature of the fire.\n> The same line as a quote string: I originally designed this foundry with the intent of building a Gingery-style lathe. Over time, I became less interested in the idea, although it still occasionally crosses my mind. Regardless, the ability to melt and cast scrap aluminum has been an indispensible part of my workshop ever since. In essence, the foundry is just a hole in the ground filled with charcoal, with two accessories. First, bricks to line the walls of the hole. These stop the dirt, which dries when heated, from collapsing inward. Second, a steel tube through which air can be blown, to increase the temperature of the fire.\n=> /link.gmi Just a link\n* a list\n* with\n* multiple elems\n```skipped...\na preformatted\ntest    -      text\n```\n"
  }
]